<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <!--
        VGG Image Annotator (VIA)
        http://www.robots.ox.ac.uk/~vgg/software/via/

        Copyright (c) 2016-2019, Abhishek Dutta, Visual Geometry Group, Oxford University.
        All rights reserved.

        Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions are met:

        Redistributions of source code must retain the above copyright notice, this
        list of conditions and the following disclaimer.
        Redistributions in binary form must reproduce the above copyright notice,
        this list of conditions and the following disclaimer in the documentation
        and/or other materials provided with the distribution.
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
        ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
        LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
        CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
        SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
        INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
        CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        POSSIBILITY OF SUCH DAMAGE.
      -->
    <title>VIA Image Annotator</title>
    <meta name="author" content="Abhishek Dutta">
    <meta name="description" content="VGG Image Annotator (VIA) is a standalone manual annotator software for image, audio and video. The full application is packaged as an offline html page of size < 400KB and runs solely from a web browser. More details are available at: http://www.robots.ox.ac.uk/~vgg/software/via/">

    <!--
    Development of VIA is supported by the EPSRC programme grant
    Seebibyte: Visual Search for the Era of Big Data (EP/M013774/1).
    Using Google Analytics, we record the usage of this application.
    A summary of this data is used in reporting of this programme grant
    and research publications related to the VIA software.

    If you do not wish to share this data, you can safely remove the
    javascript code below.
      -->
    <script type="text/javascript">
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
                               m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
                              })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

      ga('create', 'UA-20555581-2', 'auto');
      ga('set', 'page', '/via/3.0.0/via_image_annotator.html');
      ga('send', 'pageview');
    </script>

<!-- START: Contents of file: css/via_image_annotator.css-->
<style>
/*

CSS style definitions for VIA Image Annotator

Author: Abhishek Dutta <adutta@robots.ox.ac.uk>
Date: 25 May 2019
*/

html, body { margin:0; padding:0; box-sizing:border-box; font-family:Sans; font-size:1em; }

*, *:before, *:after { box-sizing:inherit; outline:none; }

.via_container { position:relative; display:grid; grid-template-rows:auto 1fr; grid-row-gap:0.5em; padding:0.3em; background-color:#ffffff; height:100vh; width:100vw; }

/* top control panel */
#via_control_panel_container { display:block; border:1px solid #707070; padding:0.4em 0.5em; }
/*#via_control_panel_container { display:block; border:1px solid #707070; padding:0.4em 0.5em; height:2.2em; overflow-y:hidden; }*/
#via_control_panel_container * { display:inline-block; margin:0 0.2em; outline:none; vertical-align:middle; }
#via_control_panel_container .spacer { padding:0 0.5em; }
#via_control_panel_container #region_info_panel { font-size:0.8em; }
#via_control_panel_container .logo { position:relative; display:inline-block; font-size:large; top:0px; }
#via_control_panel_container .logo a { text-decoration:none; }
#via_control_panel_container select { background-color:inherit; border:1px solid #cfcfcf; }
#via_control_panel_container input { background-color:inherit; border:1px solid #cfcfcf; }
#via_control_panel_container #via_project_name_input { display:inline-block; font-size:small; width:11.5em; border:none; border-bottom:1px solid #cfcfcf; }
#via_control_panel_container #via_project_name_input:hover { border-bottom:1px solid #cfcfcf; background-color:#f2f2f2; }
#via_control_panel_container #via_project_name_input:focus { background-color:#f2f2f2; border-bottom:1px solid #cfcfcf; }

/* View Container : video + temporal segmenter */
#view_container { display:grid; grid-row-gap:0.5em; } /* grid-template-rows is set dynamically */

/* view content: image, video, audio, image pair, etc. */
#view_container > .view_content_container { display:grid; } /* grid-template-rows value set by _view_annotate_single_video(), video_annotate_single_image(), etc methods in _via_view_annotator.js */

#view_container > .view_content_container > .file_container { display:block; position:relative; margin:0; padding:0; }
#view_container > .view_content_container > .file_container * { position:absolute; top:0; }
#view_container > .view_content_container > .file_container .rinput_enabled { }
#view_container > .view_content_container > .file_container > .metadata_container { display:inline-block; font-size:small; background-color:white; z-index:1001; }
#view_container > .view_content_container > .file_container > .metadata_container * { position:relative; vertical-align:middle; }
#view_container > .view_content_container > .file_container > .metadata_container table { position:relative; border-collapse:collapse; border:none; margin:0.2em; }
#view_container > .view_content_container > .file_container > .metadata_container td { vertical-align:middle; border:1px solid #cccccc; padding:0.4em 0.2em; }
#view_container > .view_content_container > .file_container > .metadata_container th { vertical-align:middle; border:1px solid #cccccc; padding:0.4em 0.2em; }
#view_container > .view_content_container > .file_container > .metadata_container input { font-size:small; }
#view_container > .view_content_container > .file_container > .metadata_container select { font-size:small; }

#view_container > .view_content_container > .file_container > .error_page { margin-left:1em; width:50em; }
#view_container > .view_content_container > .file_container > .error_page * { position:relative; }
#view_container > .view_content_container > .file_container > .error_page input { font-size:small; width:100%; }
#view_container > .view_content_container > .file_container > .error_page select { font-size:small; }
#view_container > .view_content_container > .file_container > .error_page textarea { font-size:small; }
#view_container > .view_content_container > .file_container > .error_page table { border-collapse:collapse; border:1px solid #cccccc;  width:40em;}
#view_container > .view_content_container > .file_container > .error_page td { padding:0.4em 1em; border:1px solid #cccccc; }

#view_container > .view_content_container > .file_container > .magnifier_container { position:absolute; overflow:hidden; border:2px solid red; width:30em; height:30em; }

/* metadata associated with a view */
#view_container > .view_metadata_container { padding:0.2em; border:1px solid #666666; }

/* @todo: to annotate metadata for an image pair */
.img_pair_annotator_container { display:block; max-height:100%; font-size:x-large; }
.img_pair_annotator_container input[type="radio"] { margin-left:2em; }
.img_pair_annotator_container table { margin:1em auto; }
/*
to annotate temporal segments of a video: contains 4 rows
 - row1: vtimeline_mark (video timeline current time marker)
 - row2: vtimeline (video timeline with time gradations)
 - row3: tmetadata_container (temporal segmentation metadata container)
 - row4: toolbar for temporal segmentation
*/
.temporal_segmenter_container { display:grid; grid-template-rows:auto auto 1fr auto; margin:0; padding:0; position:relative; height:100%; max-height:100%; }
.temporal_segmenter_container > .tmetadata_container { display:grid; grid-template-rows:auto 1fr; height:100%; max-height:100%; }

.temporal_segmenter_container > .tmetadata_container > .gtimeline_container { }
/*@todo: height of gmetadata_container must be fixed (and not expandable) */
.temporal_segmenter_container > .tmetadata_container > .gmetadata_container { max-height:8ch; overflow-y:auto; }
.temporal_segmenter_container > .tmetadata_container .twocolgrid { display:grid; width:100%; grid-template-columns:8em 1fr; }
.temporal_segmenter_container > .tmetadata_container .gidcol { align-self:center; padding-left:0.1em; }
.temporal_segmenter_container > .tmetadata_container .gidcol input[type="text"] { width:80%; border:none; border-bottom:1px solid #cccccc; }
.temporal_segmenter_container > .tmetadata_container .gidcol select { font-size:small; }
.temporal_segmenter_container > .tmetadata_container .gidcol input[type="text"]:focus { background-color:#f2f2f2; }

.temporal_segmenter_container > .toolbar_container { font-size:small; vertical-align:middle; margin-top:0.2em;}
.temporal_segmenter_container > .toolbar_container * { vertical-align:middle; }
.temporal_segmenter_container > .toolbar_container div { display:inline; margin-right:1em; }
.temporal_segmenter_container > .toolbar_container input[type="text"] { vertical-align:middle; font-size:small; }
.temporal_segmenter_container > .toolbar_container button { font-size:small; }
.temporal_segmenter_container > .toolbar_container select { font-size:small; }

/* Editor panel for add/update/view metadata and attributes */
#editor_container { display:block; position:absolute; bottom:0; left:0; right:0; height:40vh; overflow:auto; background-color:#f2f2f2; border-top: 0.2em solid #e6e6e6; }
#editor_container svg { font-size:large; margin:0 0.4em; }
#editor_container > .editor_content_selector { display:block; margin:0; padding:0.4em 1em;  }
#editor_container > .toolbar { position:absolute; right:1em; top:0.5em; }
#editor_container > .content_container {display:inline-block; padding:0 1em; }
#editor_container > .content_container table { border-collapse:collapse; border:1px solid #cccccc; }
#editor_container > .content_container th { border:1px solid #cccccc; padding:0.3em 0.4em; }
#editor_container > .content_container td { border:1px solid #cccccc; padding:0.3em 0.4em; }
#editor_container > .content_container .attribute_entry { display:block; margin-bottom:0.4em; }
#editor_container > .content_container input[type="text"] { width:10em; }

/* Misc */
.svg_button { position:relative; width:24px; height:24px; top:2px; stroke:none; fill:#333333; stroke-width:2px; }
.svg_button_selected { fill:#000000; stroke:none; background-color:#e6e6e6;}
.svg_button:hover { opacity:0.6; cursor:pointer; }
.svg_button:active { cursor:pointer; }
.svg_icon { position:relative; width:24px; height:24px; top:2px; }
.disabled_button { fill:#cfcfcf !important; cursor:auto !important; }
.disabled_button:hover { fill:#cfcfcf !important; cursor:auto !important; }
.disabled_button:active { fill:#cfcfcf !important; cursor:auto !important; }
.text_button { border:none; color:blue; background-color:inherit; cursor:pointer; display:inline-block; }
.text_button:hover { color:black; }

/* info pages */
#via_page_container { display:none; position:absolute; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.7); z-index:9999; text-align:center; }
#via_page_container > .via_page { position:relative; display:inline-block; color:black; background-color:white; padding:1em 2em; overflow-y:auto; text-align:left; content:"close"; width:80ch; max-height:80vh; }
#via_page_container > .via_page > .toolbar { display:block; text-align:right; }
#via_page_container > .via_page > .controls { position:relative; display:block; text-align:center; margin-top:2em; padding-top:1em; border-top:1px solid #cccccc; }
#via_page_container > .via_page > .controls button { margin:0 1em; }
#via_page_container > .via_page table { border-collapse:collapse; border:1px solid #cccccc; }
#via_page_container > .via_page th { border:1px solid #cccccc; padding:0.3em 0.4em; }
#via_page_container > .via_page td { border:1px solid #cccccc; padding:0.3em 0.4em; }
#via_page_container > .via_page li { margin:1em 0; }

/* Message Panel */
#_via_message_container { display:none; width:100vw; position:fixed; bottom:0px; z-index:9999; text-align:center; }
#_via_message_container .message_panel_close_button { position:absolute; top:-0.4em; right:0.3em; color:white; cursor:pointer; font-size:small; }
#_via_message_container #_via_message { position:relative; display:inline; margin:auto; background-color:#000000; color:#ffff00; font-size:medium; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:2rem; padding: 0.5rem 2rem;}
.key { font-family:monospace; padding:1px 6px; background:linear-gradient(to bottom,#f0f0f0,#fcfcfc);; border:1px solid #e0e0e0; white-space:nowrap; color:#303030; border-bottom-width:2px; border-radius:3px; font-size:1.2em; }

/* View manager container in the top control panel */
#view_manager_container {  }
#view_manager_container .pname { border:none; border-bottom:1px solid white; width:10em; }
#view_manager_container .pname:hover { border:none; background-color: #f2f2f2; }
#view_manager_container .pname:focus { border-bottom:1px solid #cfcfcf; }
#view_manager_container .view_selector { margin:0 0.5em; width:18em; }
#view_manager_container .view_filter_regex { width:4em; }

/* VIA info pages */
#via_start_info { display:inline-block; width:80ch; margin-left:1em; }
#via_start_info li { margin:0.5em 0; }

.hide { display:none !important; }
</style>
<!-- END: Contents of file: css/via_image_annotator.css-->
  </head>

  <body onresize="via._hook_on_browser_resize()">

    <!-- Definition of VIA Assets (e.g. about page, info page, shorcut keys, etc.) -->

    <!--
        SVG icons
        Icons prefixed with "micon_" are downloaded from https://material.io/icons
      -->
    <svg style="display:none;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <defs>
        <symbol id="shape_rectangle">
          <rect width="18" height="14" x="3" y="5" stroke="black" fill="none"></rect>
        </symbol>
        <symbol id="shape_extreme_rectangle">
          <rect width="18" height="14" x="3" y="5" stroke="black" fill="none"></rect>
          <circle r="2" cx="3"  cy="10" stroke="black" fill="grey"></circle>
          <circle r="2" cx="10"  cy="19" stroke="black" fill="grey"></circle>
          <circle r="2" cx="15" cy="5" stroke="black" fill="grey"></circle>
          <circle r="2" cx="21" cy="14" stroke="black" fill="grey"></circle>
        </symbol>
        <symbol id="shape_circle">
          <circle r="9" cx="12" cy="12" stroke="black" fill="none"></circle>
        </symbol>
        <symbol id="shape_extreme_circle">
          <circle r="9" cx="12" cy="12" stroke="black" fill="none"></circle>
          <circle r="2" cx="3" cy="10" stroke="black" fill="grey"></circle>
          <circle r="2" cx="19" cy="6" stroke="black" fill="grey"></circle>
          <circle r="2" cx="16" cy="20" stroke="black" fill="grey"></circle>
        </symbol>
        <symbol id="shape_ellipse">
          <ellipse rx="10" ry="8" cx="12" cy="12" stroke="black" fill="none"></ellipse>
        </symbol>
        <symbol id="shape_point">
          <circle r="3" cx="12" cy="12" stroke="black" fill="grey"></circle>
        </symbol>
        <symbol id="shape_polygon">
          <path d="M 4 12 L 10 2 L 20 6 L 18 16 L 8 20 z" stroke="black" fill="none"></path>
        </symbol>
        <symbol id="shape_polyline">
          <line x1="3" y1="4" x2="8" y2="18" stroke="black" fill="none"/>
          <line x1="8" y1="18" x2="14" y2="6" stroke="black"/>
          <line x1="14" y1="6" x2="20" y2="14" stroke="black"/>
          <circle r="2" cx="3" cy="4" stroke="black"></circle>
          <circle r="2" cx="8" cy="18" stroke="black"></circle>
          <circle r="2" cx="14" cy="6" stroke="black"></circle>
          <circle r="2" cx="20" cy="14" stroke="black"></circle>
        </symbol>
        <symbol id="shape_line">
          <line x1="6" y1="6" x2="19" y2="19" stroke="black"/>
          <circle r="2" cx="6" cy="6" stroke="black"></circle>
          <circle r="2" cx="19" cy="19" stroke="black"></circle>
        </symbol>

        <symbol id="micon_settings">
          <path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path>
        </symbol>
        <symbol id="micon_save">
          <path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"></path>
        </symbol>
        <symbol id="micon_open">
          <path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"></path>
        </symbol>
        <symbol id="micon_upload">
          <path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
        </symbol>
        <symbol id="micon_download">
          <path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"/>
        </symbol>
        <symbol id="micon_zoomin">
          <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
          <path d="M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/>
        </symbol>
        <symbol id="micon_zoomout">
          <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"></path>
        </symbol>
        <symbol id="micon_search">
          <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
        </symbol>
        <symbol id="micon_fit_screen">
          <path d="M17 4h3c1.1 0 2 .9 2 2v2h-2V6h-3V4zM4 8V6h3V4H4c-1.1 0-2 .9-2 2v2h2zm16 8v2h-3v2h3c1.1 0 2-.9 2-2v-2h-2zM7 18H4v-2H2v2c0 1.1.9 2 2 2h3v-2zM18 8H6v8h12V8z"/>
        </symbol>
        <symbol id="micon_delete">
          <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
        </symbol>
        <symbol id="micon_copy">
          <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path>
        </symbol>
        <symbol id="micon_paste">
          <path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path>
        </symbol>
        <symbol id="micon_insertcomment">
          <path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"></path>
        </symbol>
        <symbol id="micon_edit">
          <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
        </symbol>

        <!-- File Manager -->
        <symbol id="micon_lib_add">
          <path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/>
        </symbol>
        <symbol id="micon_add_circle">
          <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
        </symbol>
        <symbol id="micon_remove_circle">
          <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"/>
        </symbol>
        <symbol id="micon_navigate_next">
          <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
        </symbol>
        <symbol id="micon_navigate_prev">
          <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
        </symbol>
        <symbol id="micon_search">
          <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
        </symbol>
        <!-- Import/Export -->
        <symbol id="micon_import">
          <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"></path>
        </symbol>
        <symbol id="micon_export">
          <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
        </symbol>
        <symbol id="micon_import_export">
          <path d="M9 3L5 6.99h3V14h2V6.99h3L9 3zm7 14.01V10h-2v7.01h-3L15 21l4-3.99h-3z"/>
        </symbol>

        <!-- composed by Abhishek Dutta from existing materian icons, 31 Jan. 2019 -->
        <symbol id="micon_add_image">
          <path d="M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2h-3z"/>
          <path transform="translate(-9,-12) scale(1.8 1.8)" d="M5 19l3-4 2 3 3-4 4 5H5z"/>
        </symbol>
        <!-- composed by Abhishek Dutta from existing materian icons, 31 Jan. 2019 -->
        <symbol id="micon_add_media">
          <path d="M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2h-3z"/>
          <path transform="translate(-10,-7) scale(1.6 1.6)" d="M10 16.5l6-4.5-6-4.5v9z"/>
        </symbol>
        <!-- composed by Abhishek Dutta from existing materian icons, 31 Jan. 2019 -->
        <symbol id="micon_add_audio">
          <path d="M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2h-3z"/>
          <path transform="translate(-11,-4) scale(1.4 1.4)" d="M8 15c0-1.66 1.34-3 3-3 .35 0 .69.07 1 .18V6h5v2h-3v7.03c-.02 1.64-1.35 2.97-3 2.97-1.66 0-3-1.34-3-3z"/>
        </symbol>
        <!-- composed by Abhishek Dutta from existing materian icons, 13 May. 2019 -->
        <symbol id="micon_add_remote">
          <path d="M4.5 11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5v2zm2.5-.5h1.5V15H10v-4.5h1.5V9H7v1.5zm5.5 0H14V15h1.5v-4.5H17V9h-4.5v1.5zm9-1.5H18v6h1.5v-2h2c.8 0 1.5-.7 1.5-1.5v-1c0-.8-.7-1.5-1.5-1.5zm0 2.5h-2v-1h2v1z"/>
        </symbol>

        <symbol id="micon_share">
          <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/>
        </symbol>
        <!-- Video player controls -->
        <symbol id="micon_play">
          <path d="M8 5v14l11-7z"/>
        </symbol>
        <symbol id="micon_pause">
          <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
        </symbol>
        <symbol id="micon_mark_start">
          <path d="M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z"/>
        </symbol>
        <symbol id="micon_mark_end">
          <path d="M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z"/>
        </symbol>

        <!-- Temporal Segments -->
        <symbol id="micon_add">
          <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
        </symbol>

        <!-- Help -->
        <symbol id="micon_help">
          <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/>
        </symbol>

        <symbol id="micon_import_export">
          <path d="M9 3L5 6.99h3V14h2V6.99h3L9 3zm7 14.01V10h-2v7.01h-3L15 21l4-3.99h-3z"/>
        </symbol>

        <!-- Restore -->
        <symbol id="micon_restore_load">
          <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm-2 16c-2.05 0-3.81-1.24-4.58-3h1.71c.63.9 1.68 1.5 2.87 1.5 1.93 0 3.5-1.57 3.5-3.5S13.93 9.5 12 9.5c-1.35 0-2.52.78-3.1 1.9l1.6 1.6h-4V9l1.3 1.3C8.69 8.92 10.23 8 12 8c2.76 0 5 2.24 5 5s-2.24 5-5 5z"/>
        </symbol>
        <symbol id="micon_restore_save">
          <path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z"/>
        </symbol>
        <symbol id="micon_keyboard">
          <path d="M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z"/>
        </symbol>
      </defs>
    </svg>

    <!-- VIA Information Pages -->
    <div id="via_page_container">
      <div data-pageid="page_import_export" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h2>Export</h2>
        <ul>
          <li>Select Export Format:
            <select id="via_page_export_format">
              <option value="via3_csv">VIA3 (CSV)</option>
              <option value="temporal_segments_csv">Only Temporal Segments as CSV</option>
            </select>
          </li>
        </ul>
        <h2>Import</h2>
        <ul>
          <li>VIA Shared Project: <input id="via_page_import_pid" type="text" placeholder="e.g. 71578187-3cd3-45d0-8198-7c441fbc06af" style="width:25em;">
          </li>
          <li>VIA2 project created (json file):
            <input id="via_page_import_via2_project_json" type="file">
          </li>
        </ul>

        <div class="controls">
          <button id="via_page_button_import" onclick="_via_util_page_process_action(event)">Import</button>
          <button id="via_page_button_export" onclick="_via_util_page_process_action(event)">Export</button>
          <button onclick="_via_util_page_hide()">Cancel</button>
        </div>
      </div>

      <div data-pageid="page_fileuri_bulk_add" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <p>File Type:&nbsp;<select id="via_page_fileuri_filetype">
          <option value="2" selected>Image</option>
          <option value="4">Video</option>
          <option value="8">Audio</option>
          <option value="0">Detect Automatically</option>
          </select>
        </p>
        <h2>Paste File URI (one URI per line)</h2>
        <textarea id="via_page_fileuri_urilist" placeholder="For example, (1) http://www.robots.ox.ac.uk/~vgg/software/via/images/via_logo.png ; (2) file:///data/images/img001.jpg ; (3) file:///C:/Documents%20and%20Settings/tlm/video001.mp4" rows="10" cols="80"></textarea>
        <h2>Import URI from a File</h2>
        <input id="via_page_fileuri_importfile" type="file">

        <div class="controls">
          <button id="via_page_fileuri_button_bulk_add" onclick="_via_util_page_process_action(event)">Add</button>
          <button onclick="_via_util_page_hide()">Cancel</button>
        </div>
      </div>

      <div data-pageid="page_share_not_shared_yet" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h2>Sharing this Project</h2>
        <p>This project has not been shared yet. If you want to share this project with others and allow them to contribute to this VIA project, click <svg class="svg_icon" viewbox="0 0 24 24"><use xlink:href="#micon_upload"></use></svg> button in the toolbar.</p>
        <h2>Loading a Shared Project</h2>
        <p>A unique project-id is assigned to every shared VIA project. You must have received such a unique project-id when someone asked you to contribute to a VIA project. Click <svg class="svg_icon" viewbox="0 0 24 24"><use xlink:href="#micon_download"></use></svg> button in the toolbar to open a shared project.</p>
        <p>If you do not have access to a shared project-id and you can explore the following publicly shared demonstration projects:
          <ul>
            <li>Image Annotation: 42be7c9f-f305-49f9-8de1-3717459b1306</li>
          </ul>
        </p>

        <div class="controls">
          <button onclick="_via_util_page_hide()">Close</button>
        </div>
      </div>

      <div data-pageid="page_share_already_shared" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <p style="color:red;">This feature is not stable yet. If you encounter any issues, please <a href="https://gitlab.com/vgg/via/issues">report</a> it.</p>
        <h3>Information about this shared project</h3>
        <p id="via_page_share_project_info"></p>
        <h3>How can others contribute to this project?</h3>
        <p>This project has already been shared and therefore anyone can contribute to this project. To contribute to this project, other users should to follow these steps:
          <ol>
            <li>Open the VIA application (version 3.0.3 or higher) in a web browser.</li>
            <li>Click <svg class="svg_icon" onclick="" viewbox="0 0 24 24"><use xlink:href="#micon_download"></use><title>Share this project and your updates with others</title></svg> button in the top toolbar.</li>
            <li>Enter the following project-id: <span id="via_page_share_project_id"></span></li>
            <li>Make changes to the project and click <svg class="svg_icon" viewbox="0 0 24 24"><use xlink:href="#micon_upload"></use></svg> to share your updates with others.</li>
          </ol>
        </p>
        <h3>Important Notes</h3>
        <ul>
          <li>Do not store private or confidential information in a shared VIA project. Furthermore, be careful when you share your project-id with others as it allows them to make any changes to your project.</li>
          <li>The VIA servers do not maintain backup copy of the shared VIA projects. In the event of disk failure, all data will be lost. So, we strongly advise you to always keep a local copy of your project data.</li>
          <li>We <strong>cannot guarantee</strong> 24/7 availability of VIA project share servers. In the event of hardware or disk failure, the VIA project share servers will be offline for an extended period of time.</li>
          <li>This VIA share feature should <strong>not</strong> be used for large scale collaborative annotation projects. For such use cases, we advise you to setup a dedicated server with sufficient backup and secutiry.</li>
          <li>The shared VIA project should not exceed 1MB in size.</li>
        </ul>
        <div class="controls">
          <button onclick="_via_util_page_hide()">Close</button>
        </div>
      </div>

      <div data-pageid="page_share_open_shared" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h2>Open a Shared VIA Project</h2>
        <p>A unique project-id is assigned to every shared VIA project. For example, the following two project-id have been publicly shared for demonstration purposes:
          <ul>
            <li>Image Annotation: 42be7c9f-f305-49f9-8de1-3717459b1306</li>
          </ul>
        </p>
        <p>To open a shared project, enter the project-id below:</p>
        <table>
          <tr>
            <td><label for="via_page_input_pid">VIA Project Id</label></td>
            <td>
              <input style="width:25em;" type="text" placeholder="e.g. e302eadf-aa53-4a5a-b958-11175692c928" id="via_page_input_pid">
            </td>
          </tr>
        </table>

        <div class="controls">
          <button id="via_page_button_open_shared" onclick="_via_util_page_process_action(event)">Open Shared Project</button>
          <button onclick="_via_util_page_hide()">Cancel</button>
        </div>
      </div>

      <div data-pageid="page_demo_instructions" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h1>Some Quick Tips</h1>
        <ul>
          <li>Click and drag mouse cursor to define a region around an object.</li>
          <li>Use the <span class="text_button" onclick="via.editor.show()">attribute editor</span> to define or update attributes (e.g. name, colour, etc) of user defined regions.</li>
        </ul>
      </div>

      <div data-pageid="page_keyboard_shortcut" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h1>Keyboard Shortcuts</h1>
        <h3>General</h3>
        <table>
          <thead>
            <tr>
              <th>Command</th>
              <th>Shortcut</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>Move to next/previous image</td><td><span class="key">n</span> / <span class="key">p</span></td></tr>
            <tr><td>Move selected region by 1 pixel (by 10 pixels by pressing Shift key)</td><td><span class="key">Shift</span> + <span class="key">&larr;</span> / <span class="key">&rarr;</span> / <span class="key">&uarr;</span> / <span class="key">&darr;</span></td></tr>
            <tr><td>Delete selected region</td><td><span class="key">Backspace</span> or <span class="key">Delete</span></td></tr>
            <tr><td>Select all region</td><td><span class="key">Ctrl</span> + a</td></tr>
            <tr><td>Toggle visibility of region shape boundary</td><td><span class="key">b</span></td></tr>
            <tr><td>Toggle visibility of region label</td><td><span class="key">l</span></td></tr>
          </tbody>
        </table>
        <p>&nbsp;</p>
      </div>

      <div data-pageid="page_about" class="via_page">
        <div class="toolbar"><span onclick="_via_util_page_hide()" class="text_button">&times;</span></div>
        <h2>VGG Image Annotator (VIA)</h2>
        <p>Version: <a href="https://gitlab.com/vgg/via/blob/master/CHANGELOG">__VIA_VERSION__</a></p>
        <p>VGG Image Annotator (VIA) is a simple and standalone manual annotation tool
          for image, audio and video. The VIA software is a light weight, standalone
          and offline software package that does not require any installation or setup
          and runs solely in a web browser. The complete VIA software fits in a single
          self-contained HTML page of size less than 500 kilobyte that runs as an
          offline application in most modern web browsers. VIA software is an open
          source project created solely using HTML, Javascript and CSS. More details
          about VIA is available from <a href="http://www.robots.ox.ac.uk/~vgg/software/via">http://www.robots.ox.ac.uk/~vgg/software/via</a>.</p>
        <h4>Open Source Ecosystem</h4>
        <p>We have nurtured a large and thriving open source community which not only
          provides feedback but also contributes code to add new features and improve
          existing features in the VIA software. The open source ecosystem of VIA
          thrives around its <a href="https://gitlab.com/vgg/via">source code repository</a>
          hosted by the Gitlab platform. Most of our users report issues and request
          new features for future releases using the
          <a href="https://gitlab.com/vgg/via/issues">issue portal</a>. Many of our
          users not only submit bug reports but also suggest a potential fix for
          these software issues. Some of our users also contribute code to add new
          features to the VIA software using the
          <a href="https://gitlab.com/vgg/via/merge_requests">merge request</a> portal.
          A list of our contributors is available
          <a href="https://gitlab.com/vgg/via/blob/master/Contributors.md">here</a>.</p>

        <p>Thanks to the flexibility provided by our BSD open source software
          <a href="https://gitlab.com/vgg/via/blob/master/LICENSE">license</a>, many
          industrial projects have adapted the VIA software for internal or commercial use.</p>

        <h4>License</h4>
        <pre>
Copyright (c) 2019-2021, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
        </pre>
        <p>Copyright &copy; 2019-2021, <a href="mailto:adutta-removeme@robots.ox.ac.uk">Abhishek Dutta</a>, Visual Geometry Group, Oxford University and VIA Contributors.</p>
      </div>
    </div> <!-- end of page container -->

    <!-- used by _via_view_annotator._show_start_info() -->
    <div id="via_start_info_content" class="hide">
      <ul>
        <li>To start annotation of an image, audio and video, select <span class="text_button" onclick="via.vm._on_add_media_local()">local files</span> or <span class="text_button" onclick="via.vm._on_add_media_remote()">add files</span> (local files using file:// and remote files using http:// URI). You can also add a list of file URI in <span class="text_button" onclick="via.vm._on_add_media_bulk()">bulk</span>.</li>
        <li>Press <span class="key">Space</span> key to play or pause an audio or video at any time (more keyboard <span class="text_button" onclick="_via_util_page_show('page_keyboard_shortcut')">shortcuts</span>).</li>
        <li>Spatial regions (e.g. 50x80 pixel rectangular bounding box) can be defined for image and video frame when video is paused. Temporal segments (e.g. video segment from time 3.1 sec. to 12.5 sec) can be defined for audio and video files.</li>
        <li>Use the <span class="text_button" onclick="via.editor.show()">attribute editor</span> to define or update attributes (e.g. name, colour, etc.) of user defined regions.</li>
        <li>You can also try preloaded demo for <a target="_blank" href="http://www.robots.ox.ac.uk/~vgg/software/via/demo/via_image_annotator.html">image</a>, <a target="_blank" href="http://www.robots.ox.ac.uk/~vgg/software/via/demo/via_audio_annotator.html">audio</a> and <a target="_blank" href="http://www.robots.ox.ac.uk/~vgg/software/via/demo/via_video_annotator.html">video</a> annotation.</li>
      </ul>
    </div>

    <!-- VIA dynamically populates this container with control panel, media (image, video, etc), etc. -->
    <div class="via_container" id="via_container"></div>

<!-- START: Contents of file: js/_via_util.js-->
<script>
/**
 * Utilities used by VIA default user interface
 *
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 31 Dec. 2018
 *
 */

'use strict'

var _via_msg_clear_timer; // holds a reference to current message timoout
var _via_page_current;    // holds a reference to current info page
var _via_page_action_map; // holds a reference to action map for current info page

function _via_util_get_svg_button(icon_id, title, id) {
  var el = document.createElementNS(_VIA_SVG_NS, 'svg');
  el.setAttributeNS(null, 'viewBox', '0 0 24 24');
  el.innerHTML = '<use xlink:href="#' + icon_id + '"></use><title>' + title + '</title>';
  el.setAttributeNS(null, 'class', 'svg_button');
  if ( typeof(id) !== 'undefined' ) {
    el.setAttributeNS(null, 'id', id);
  }
  return el;
}

function _via_util_get_html_input_element_value(el) {
  switch(el.tagName) {
  case 'TEXTAREA':
    return el.value;

  case 'SELECT':
    return el.options[el.selectedIndex].value;

  case 'INPUT':
    return el.value;
  }
}

function _via_util_get_filename_from_uri(uri) {
  if ( uri.includes('/') ) {
    var tokens = uri.split('/');
    return tokens[ tokens.length - 1 ];
  } else {
    return uri;
  }
}

function _via_util_file_type_to_str(type) {
  switch(type) {
    case _VIA_FILE_TYPE.IMAGE:
      return 'image';
      break;
    case _VIA_FILE_TYPE.VIDEO:
      return 'video';
      break;
    case _VIA_FILE_TYPE.AUDIO:
      return 'audio';
      break;
    default:
      return 'unknown ' + type;
  }
}

function _via_util_file_type_str_to_id(type) {
  switch(type) {
    case 'image':
      return _VIA_FILE_TYPE.IMAGE;
      break;
    case 'video':
      return _VIA_FILE_TYPE.VIDEO;
      break;
    case 'audio':
      return _VIA_FILE_TYPE.AUDIO;
      break;
    default:
      return -1;
  }
}

function _via_util_file_loc_to_str(loc) {
  switch(loc) {
    case _VIA_FILE_TYPE.LOCAL:
      return 'local';
      break;
    case _VIA_FILE_TYPE.URIHTTP:
      return 'http://';
      break;
    case _VIA_FILE_TYPE.URIFILE:
      return 'file://';
      break;
    case _VIA_FILE_TYPE.URIFILE:
      return 'inline';
      break;
    default:
      return 'unknown-type-id=' + type.toString();
  }
}

function _via_util_file_loc_str_to_id(loc) {
  switch(loc) {
    case 'local':
      return _VIA_FILE_TYPE.LOCAL;
      break;
    case 'http://':
      return _VIA_FILE_TYPE.URIHTTP;
      break;
    case 'file://':
      return _VIA_FILE_TYPE.URIFILE;
      break;
    case 'inline':
      return _VIA_FILE_TYPE.URIFILE;
      break;
    default:
      return -1;
  }
}


function _via_util_metadata_shape_str(shape_id) {
  switch(shape_id) {
    case _VIA_WHERE_SHAPE.TIME:
      return 'time';
      break;
    case _VIA_WHERE_SHAPE.RECTANGLE:
      return 'rectangle';
      break;
    case _VIA_WHERE_SHAPE.EXTREME_RECTANGLE:
      return 'extreme_rectangle';
      break;
    case _VIA_WHERE_SHAPE.CIRCLE:
      return 'circle';
      break;
    case _VIA_WHERE_SHAPE.EXTREME_CIRCLE:
      return 'extreme_circle';
      break;
    case _VIA_WHERE_SHAPE.ELLIPSE:
      return 'ellipse';
      break;
    case _VIA_WHERE_SHAPE.POINT:
      return 'point';
      break;
    case _VIA_WHERE_SHAPE.POLYLINE:
      return 'polyline';
      break;
    case _VIA_WHERE_SHAPE.POLYGON:
      return 'polygon';
      break;
    default:
      return 'unknown';
  }
}

function _via_util_escape_quote_for_csv(s) {
  return s.replace(/["]/g, '""');
}

function _via_util_load_text_file(text_file, callback_function) {
  if (text_file) {
    var text_reader = new FileReader();

    text_reader.addEventListener( 'error', function() {
      console.log('Error loading data text file :  ' + text_file.name + ' !');
      callback_function('');
    }, false);

    text_reader.addEventListener( 'load', function() {
      callback_function(text_reader.result);
    }, false);
    text_reader.readAsText(text_file, 'utf-8');
  }
}

function _via_util_file_ext(filename) {
  return filename.substr( filename.lastIndexOf('.') + 1 );
}

function _via_util_infer_file_loc_from_filename(filename) {
  if ( filename.startsWith('http://') || filename.startsWith('https://') ) {
    return _VIA_FILE_LOC.URIHTTP;
  } else {
    if ( filename.startsWith('file://') || filename.includes('/') ) {
      return _VIA_FILE_LOC.URIFILE;
    } else {
      return _VIA_FILE_LOC.LOCAL;
    }
  }
}

function _via_util_infer_file_type_from_filename(filename) {
  var ext = _via_util_file_ext(filename);
  switch( ext.toLowerCase() ) {
  case 'ogv':
  case 'mp4':
  case 'avi':
  case 'webm':
    return _VIA_FILE_TYPE.VIDEO;
    break;
  case 'jpg':
  case 'jpeg':
  case 'png':
  case 'bmp':
    return _VIA_FILE_TYPE.IMAGE;
    break;
  case 'mp3':
  case 'wav':
  case 'oga':
  case 'ogg':
    return _VIA_FILE_TYPE.AUDIO;
  }
}


function _via_util_download_as_file(data, filename) {
  var a      = document.createElement('a');
  a.href     = URL.createObjectURL(data);
  a.download = filename;

  // simulate a mouse click event
  var event = new MouseEvent('click', {
    view: window,
    bubbles: true,
    cancelable: true
  });
  a.dispatchEvent(event);
}

function _via_util_file_select_local(type, handler, multiple) {
  var fsel = document.createElement('input');
  fsel.setAttribute('type', 'file');
  fsel.setAttribute('name', 'files[]');
  if ( typeof(multiple) === 'undefined' ||
       multiple === true ) {
    fsel.setAttribute('multiple', 'multiple')
  }

  switch(type) {
  case _VIA_FILE_SELECT_TYPE.IMAGE:
    fsel.accept = 'image/*';
    break;
  case _VIA_FILE_SELECT_TYPE.VIDEO:
    fsel.accept = 'video/*';
    break;
  case _VIA_FILE_SELECT_TYPE.AUDIO:
    fsel.accept = 'audio/*';
    break;
  case _VIA_FILE_SELECT_TYPE.TEXT:
    fsel.accept = '.csv,.txt';
    break;
  case _VIA_FILE_SELECT_TYPE.JSON:
    fsel.accept = '.json';
    break;
  case _VIA_FILE_SELECT_TYPE.VIDEO | _VIA_FILE_SELECT_TYPE.AUDIO:
    fsel.accept = 'video/*,audio/*';
    break;
  case _VIA_FILE_SELECT_TYPE.VIDEO | _VIA_FILE_SELECT_TYPE.AUDIO | _VIA_FILE_SELECT_TYPE.IMAGE:
    fsel.accept = 'video/*,audio/*,image/*';
    break;
  case _VIA_FILE_SELECT_TYPE.WEBVTT:
    fsel.accept = '.vtt';
    break;
  }

  fsel.onchange = handler;
  fsel.click();
}

// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function _via_util_rand_int(min_inclusive, max_exclusive) {
  return Math.floor(Math.random() * (max_exclusive - min_inclusive)) + min_inclusive;
}

function _via_util_page_show(page_id, action_map) {
  _via_page_action_map = action_map;
  var el = document.getElementById('via_page_container');

  var pages = el.getElementsByClassName('via_page');
  var n = pages.length;
  for ( var i = 0; i < n; ++i ) {
    if ( pages[i].dataset.pageid === page_id ) {
      pages[i].style.display = 'inline-block';
      el.style.display = 'block';
      _via_page_current = pages[i];
    } else {
      pages[i].style.display = 'none';
    }
  }
}

function _via_util_page_process_action(e) {
  for ( var action_id in _via_page_action_map ) {
    if ( e.target.id === action_id ) {
      var page_data = _via_util_page_gather_user_input();
      page_data['_action_id'] = e.target.id;
      _via_page_action_map[action_id].call(this, page_data);
      _via_util_page_hide();
    }
  }
}

function _via_util_page_gather_user_input() {
  var user_input = {};

  // dropdown
  var select_list = _via_page_current.getElementsByTagName('select');
  for ( var i = 0; i < select_list.length; ++i ) {
    user_input[select_list[i].id] = select_list[i].options[ select_list[i].selectedIndex ].value;
  }

  // input
  var input_list = _via_page_current.getElementsByTagName('input');
  for ( var i = 0; i < input_list.length; ++i ) {
    switch(input_list[i].type) {
      case 'file':
        user_input[input_list[i].id] = input_list[i].files;
        break;
      case 'select':
        if ( input_list[i].selectedIndex === -1 ) {
          user_input[input_list[i].id] = '';
        } else {
          user_input[input_list[i].id] = input_list[i].options[input_list[i].selectedIndex];
        }
        break;
      case 'text':
      case 'password':
        user_input[input_list[i].id] = input_list[i].value;
        break;
      default:
        console.warn('Input type=' + input_list[i].type + ' not yet implemented!');
    }
  }

  // textarea
  var textarea_list = _via_page_current.getElementsByTagName('textarea');
  for ( var i = 0; i < textarea_list.length; ++i ) {
    user_input[textarea_list[i].id] = textarea_list[i].value;
  }

  return user_input;
}

function _via_util_page_hide() {
  var el = document.getElementById('via_page_container');
  if ( el.style.display === 'block' ) {
    el.style.display = 'none';
  }
  _via_page_current = null;
}

function _via_util_msg_show(msg, sticky) {
  var container = document.getElementById('_via_message_container');
  var content = document.getElementById('_via_message');
  if ( container && content ) {
    if ( _via_msg_clear_timer ) {
      clearTimeout(_via_msg_clear_timer);
    }
    if ( typeof(sticky) === 'undefined' ||
         sticky === false
       ) {
      _via_msg_clear_timer = setTimeout( function() {
        document.getElementById('_via_message_container').style.display = 'none';
      }, _VIA_CONFIG.MSG_TIMEOUT);
    }

    content.innerHTML = msg + '<span class="message_panel_close_button">&times;</span>';
    container.style.display = 'block';
  }
}

function _via_util_msg_hide() {
  document.getElementById('_via_message_container').style.display = 'none';
  if ( _via_msg_clear_timer ) {
    clearTimeout(_via_msg_clear_timer);
  }
}

function _via_util_pad10(x) {
  if ( x < 10 ) {
    return '0' + x.toString();
  } else {
    return x;
  }
}

function _via_util_date_to_filename_str(date_str) {
  var t = new Date(date_str);
  var month_list = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return _via_util_pad10(t.getDate()) + month_list[t.getMonth()] + t.getFullYear() + '_' + _via_util_pad10(t.getHours()) + 'h' + _via_util_pad10(t.getMinutes()) + 'm' + _via_util_pad10(t.getSeconds())+'s';
}

function _via_util_remote_get(uri) {
  return new Promise( function(ok_callback, err_callback) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('load', function() {
      ok_callback(xhr.responseText);
    });
    xhr.addEventListener('timeout', function(e) {
      err_callback(e);
    });
    xhr.addEventListener('error', function(e) {
      err_callback(e)
    });
    xhr.open('GET', uri);
    xhr.send();
  });
}

function _via_util_remote_head(uri) {
  return new Promise( function(ok_callback, err_callback) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('load', function() {
      switch(xhr.statusText) {
        case 'OK':
          ok_callback(xhr.responseText);
          break;
        default:
          err_callback(xhr.statusText);
      }
    });
    xhr.addEventListener('timeout', function(e) {
      err_callback(e);
    });
    xhr.addEventListener('error', function(e) {
      err_callback(e)
    });
    xhr.open('HEAD', uri);
    xhr.send();
  });
}

function _via_util_float_arr_to_fixed(arr, fixed) {
  var farr = [];
  for ( var i in arr ) {
    farr[i] = parseFloat( arr[i].toFixed(fixed) );
  }
  return farr;
}

function _via_util_float_to_fixed(value, fixed) {
  return parseFloat( value.toFixed(fixed) );
}


//
// Unique Id
//

// URL.createObjectURL() produces a unique id every time it is invoked.
// We use this functionality to generate unique id required by VIA
// @todo: Replace with a true UUID generator if it can be efficiently generated
// using pure JS (no dependencies)
function _via_util_uuid() {
  var temp_url = URL.createObjectURL(new Blob())
  var uuid = temp_url.toString();
  URL.revokeObjectURL(temp_url);
  var slash_index = uuid.lastIndexOf('/');
  if ( uuid !== -1 ) {
    // remove any prefix (e.g. blob:null/, blob:www.test.com/, ...)
    uuid = uuid.substr(slash_index + 1);
    uuid = uuid.replace(/-/g, '');
  }
  return uuid;
}

function _via_util_gen_project_id() {
  return 'via' + _via_util_uuid();
}

function _via_util_uid6() {
  var temp_url = URL.createObjectURL(new Blob());
  var uuid = temp_url.toString();
  URL.revokeObjectURL(temp_url);
  var n = uuid.length;
  // remove any prefix (e.g. blob:null/, blob:www.test.com/, ...)
  var uuid_suffix_str = '';
  for ( var i = n - 12; i < n; i = i + 2 ) {
    uuid_suffix_str += String.fromCharCode( parseInt(uuid.substr(i, 2), 16) );
  }
  return btoa(uuid_suffix_str).replace(/[-+/_]/gi, 'X');
}

function _via_util_array_eq(a, b) {
  if ( a == null || b == null ) {
    return false;
  }
  if ( a.length != b.length ) {
    return false;
  }
  var n = a.length;
  for ( var i = 0; i < n; ++i ) {
    if ( a[i] !== b[i] ) {
      return false;
    }
  }
  return true;
}

function _via_util_obj_to_csv(obj, default_key) {
  var csv = [];
  for ( var oid in obj ) {
    if ( oid === default_key ) {
      csv.push( '*' + obj[oid] );
    } else {
      csv.push( obj[oid] );
    }
  }
  return csv.join(',');
}

function _via_util_attribute_to_html_element(attr) {
  var el;
  switch(attr.type) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    el = document.createElement('textarea');
    break;

  case _VIA_ATTRIBUTE_TYPE.SELECT:
    el = document.createElement('select');
    var oid;
    for ( oid in attr.options ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', oid);
      oi.innerHTML = attr.options[oid];
      if ( oid == attr.default_option_id ) {
        oi.setAttribute('selected', '');
      }
      el.appendChild(oi);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.RADIO:
    el = document.createElement('table');
    for ( var oid in attr.options ) {
      var oi = document.createElement('input');
      oi.setAttribute('name', attr.aname);
      oi.setAttribute('type', 'radio');
      var label = document.createElement('label');
      label.innerHTML = attr.options[oid];
      var tr = document.createElement('tr');
      var td = document.createElement('td');
      td.appendChild(oi);
      td.appendChild(label);
      tr.appendChild(td);
      el.appendChild(tr);
    }
    break;

  default:
    el = document.createElement('span');
    el.innerHTML = 'UNKNOWN';
  }
  return el;
}

// ensure the exported json string conforms to RFC 4180
// see: https://en.wikipedia.org/wiki/Comma-separated_values
function _via_util_obj2csv(d) {
  return '"' + JSON.stringify(d).replace(/["]/g, '""') + '"';
}

function _via_util_merge_object(obj1, obj2) {
  var merged_obj = {};
  Object.assign(merged_obj, obj1);
  Object.assign(merged_obj, obj2);
  return merged_obj;
}

function _via_util_collect_object_by_keys(object, keys) {
  var merged_obj = {};
  for ( var kindex in keys ) {
    Object.assign(merged_obj, obj1);
  }
  Object.assign(merged_obj, obj1);
  Object.assign(merged_obj, obj2);
  return merged_obj;
}

function _via_util_clamp(x, lower_bound, upper_bound) {
  return Math.min( Math.max(x, lower_bound), upper_bound );
}

function _via_util_merge_three_way_str(ancestor, version1, version2) {
  if ( version1 === version2 ) {
    return version1;
  } else {
    if ( ancestor === version1 ) {
      return version2;
    } else {
      return version1;
    }
  }
}


function _via_seconds_to_hh_mm_ss_ms(s) {
  // result = [hh, mm, ss, ms]
  var result = [0, 0, 0, 0];
  var sec = parseFloat(s);
  var ms = parseInt((sec - parseInt(s)) * 1000);
  if(ms < 99) {
    if(ms < 9) {
      result[3] = '00' + ms.toString();
    } else {
      result[3] = '0' + ms.toString();
    }
  } else {
    result[3] = ms.toString();
  }

  if(sec > 60*60) {
    var hh = parseInt(sec / (60*60));
    if(hh>9) {
      result[0] = hh.toString();
    } else {
      result[0] = '0' + hh;
    }
    sec = sec - result[0] * 60 * 60;
  } else {
    result[0] = '00';
  }

  if(sec > 60) {
    var mm = parseInt(sec / 60);
    if(mm>9) {
      result[1] = mm.toString();
    } else {
      result[1] = '0' + mm;
    }
    sec = sec - result[1] * 60;
  } else {
    result[1] = '00';
  }

  var ss = parseInt(sec);
  if(ss>9) {
    result[2] = ss.toString();
  } else {
    result[2] = '0' + ss;
  }

  return result;
}

function _via_hh_mm_ss_ms_to_seconds(hh_mm_ss_ms) {
  // hh_mm_ss_ms = HH:MM:SS.MS
  var split1 = hh_mm_ss_ms.split('.');
  if(split1.length !== 2) {
    split1 = hh_mm_ss_ms.split(',');
    if(split1.length !== 2) {
      return -1;
    }
  }
  var split2 = split1[0].split(':');
  if(split2.length !== 3) {
    return -1;
  }
  var sec = 0;
  switch(split2.length) {
  case 3:
    sec = parseInt(split2[0]) * 60 * 60 + parseInt(split2[1]) * 60 + parseInt(split2[2]);
    break;
  case 2:
    sec = parseInt(split2[1]) * 60 + parseInt(split2[2]);
    break;
  case 1:
    sec = parseInt(split2[2]);
    break;
  }
  if(split1.length === 2) {
    sec = sec + parseInt(split1[1])/1000;
  }
  return sec;
}
</script>
<!-- END: Contents of file: js/_via_util.js-->
<!-- START: Contents of file: js/_via_const.js-->
<script>
'use strict'

// definition of contants that are used by all modules
const _VIA_USER_ROLE = { 'ADMIN':1, 'ANNOTATOR':2, 'REVIEWER':3 };
const _VIA_STORE_TYPE = { 'LOCALSTORAGE':1, 'COUCHDB':2 };

var _VIA_SVG_NS = "http://www.w3.org/2000/svg";

var _VIA_PROJECT_ID_MARKER = '__VIA_PROJECT_ID__';
var _VIA_PROJECT_REV_ID_MARKER = '__VIA_PROJECT_REV_ID__';
var _VIA_PROJECT_REV_TIMESTAMP_MARKER = '__VIA_PROJECT_REV_TIMESTAMP__';

var _VIA_FILE_SELECT_TYPE = { 'JSON':2, 'CSV':4, 'TEXT':8, 'IMAGE':16, 'VIDEO':32, 'AUDIO':64, 'WEBVTT':128 };
var _VIA_MERGE_STRATEGY = { 'THREE_WAY':1 }

// zoom, used in _via_control_panel.js to populate a dropdown of zoom options
var _VIA_ZOOM_MODE = {'FITHEIGHT':1, 'FITWIDTH':2, 'SCALE':3};
var _VIA_DEFAULT_ZOOM_MODE = _VIA_ZOOM_MODE.FITHEIGHT;
var _VIA_ZOOM_SCALE_VALUE_LIST = [0.2, 0.5, 0.8, 1.0, 2.0, 4.0, 8.0];
var _VIA_ZOOM_SCALE_DEFAULT_INDEX = 3;
</script>
<!-- END: Contents of file: js/_via_const.js-->
<!-- START: Contents of file: js/_via_config.js-->
<script>
'use strict'

const _VIA_NAME = 'VGG Image Annotator';
const _VIA_NAME_SHORT = 'VIA';
const _VIA_VERSION = '3.0.11';
const _VIA_URL_CODE_REPO = 'https://gitlab.com/vgg/via';
const _VIA_URL_PROJECT_PAGE = 'https://www.robots.ox.ac.uk/~vgg/software/via/';

const _VIA_CONFIG = { MSG_TIMEOUT:3000 };

// VIA Project Server (VPS)
// Use the https://zeus.robots.ox.ac.uk/via/store/3.x.y/ by default
// For older versions, the http://zeus.robots.ox.ac.uk/via/store/3.x.y/ remains available
// to host your own server, see https://gitlab.com/vgg/vps
const _VIA_REMOTE_STORE = 'https://zeus.robots.ox.ac.uk/via/store/3.x.y/';
//const _VIA_REMOTE_STORE = 'http://zeus.robots.ox.ac.uk/via/store/3.x.y/'; // use if http:// only access is required
const _VIA_REMOTE_TIMEOUT = 6000; // in milliseconds
//const _VIA_REMOTE_STORE = 'http://localhost:9669/'; // for debug

const _VIA_DEFAULT_ATTRIBUTE_ANCHOR_ID = '';

var _VIA_FLOAT_FIXED_POINT = 5; // floats are stored as 5 decimal places
var _VIA_SPATIAL_REGION_MOVE_DELTA = 10; // in pixels
var _VIA_SPATIAL_REGION_LABEL_MAXLENGTH = 12; // in characters
var _VIA_SPATIAL_REGION_LABEL_FONT = '12px Sans';
</script>
<!-- END: Contents of file: js/_via_config.js-->

<!-- START: Contents of file: js/_via_event.js-->
<script>
/**
 * @class
 * @classdesc Implements a basic event emitter and event listener
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @since 15 Jan. 2019
 */

'use strict';

function _via_event() {
  this.on_event       = _via_event.prototype.on_event;
  this.emit_event     = _via_event.prototype.emit_event;
  this.disable_events = _via_event.prototype.disable;
  this.enable_events  = _via_event.prototype.enable;
  this.clear_events   = _via_event.prototype.clear;
  this.delete_listener  = _via_event.prototype.delete_listener;

  this._event = { 'enabled':true, 'targets':{} };
}

_via_event.prototype.on_event = function(event_id, listener_name, listener_method, listener_param) {
  // initialise event handlers data structure (if not exist)
  if ( typeof(this._event.targets[event_id]) === 'undefined' ) {
    this._event.targets[event_id] = { 'listener_name_list':[],
                                      'listener_method_list':[],
                                      'listener_param_list':[] };
  }

  this._event.targets[event_id].listener_name_list.push( listener_name );
  this._event.targets[event_id].listener_method_list.push( listener_method );
  if ( typeof(listener_param) === 'undefined' ) {
    this._event.targets[event_id].listener_param_list.push( {} );
  } else {
    this._event.targets[event_id].listener_param_list.push( listener_param );
  }
}

_via_event.prototype.emit_event = function(event_id, event_payload) {
  if ( this._event.enabled ) {
    if ( typeof(this._event.targets[event_id]) !== 'undefined' ) {
      for ( var i = 0; i < this._event.targets[event_id].listener_name_list.length; ++i ) {
        this._event.targets[event_id].listener_method_list[i].call(this, this._event.targets[event_id].listener_param_list[i], event_payload);
      }
    }
  }
}

_via_event.prototype.disable = function() {
  this._event.enabled = false;
}

_via_event.prototype.enable = function() {
  this._event.enabled = true;
}

_via_event.prototype.clear = function(listener_name, event_id) {
  if ( typeof(listener_name) === 'undefined' ) {
    if ( typeof(event_id) === 'undefined' ) {
      this._event.targets = {}; // clear all listeners
    } else {
      // clear based on event_id
      this._event.targets[event_id] = { 'listener_name_list':[],
                                        'listener_method_list':[],
                                        'listener_param_list':[] };
    }
  } else {
    if ( typeof(event_id) === 'undefined' ) {
      // clear based on listener_name
      for ( var event_id in this._event.targets ) {
        var delete_index_list = [];
        for ( var i = 0; i < this._event.targets[event_id].listener_name_list.length; ++i ) {
          if ( this._event.targets[event_id].listener_name_list[i] === listener_name ) {
            delete_index_list.push(i);
          }
        }
        if ( delete_index_list.length ) {
          for ( var i = 0; i < delete_index_list.length; ++i ) {
            this.delete_listener(event_id, delete_index_list[i]);
          }
        }
      }
    } else {
      // clear based on both listener_name and event_id
      var delete_index = -1
      for ( var i = 0; i < this._event.targets[event_id].listener_name_list.length; ++i ) {
        if ( this._event.targets[event_id].listener_name_list[i] === listener_name ) {
          delete_index = i;
          break;
        }
      }
      this.delete_listener(event_id, delete_index);
    }
  }
}

_via_event.prototype.delete_listener = function(event_id, listener_index) {
  this._event.targets[event_id].listener_name_list.splice(listener_index, 1);
  this._event.targets[event_id].listener_method_list.splice(listener_index, 1);
  this._event.targets[event_id].listener_param_list.splice(listener_index, 1);
}
</script>
<!-- END: Contents of file: js/_via_event.js-->
<!-- START: Contents of file: js/_via_control_panel.js-->
<script>
/**
 *
 * @class
 * @classdesc VIA Control Panel
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 16 May 2019
 *
 */

function _via_control_panel(control_panel_container, via) {
  this._ID = '_via_control_panel_';
  this.c   = control_panel_container;
  this.via = via;

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );

  this._init();
}

_via_control_panel.prototype._init = function(type) {
  this.c.innerHTML = '';

  var logo_panel = document.createElement('div');
  logo_panel.setAttribute('class', 'logo');
  logo_panel.innerHTML = '<a href="http://www.robots.ox.ac.uk/~vgg/software/via/" title="VGG Image Annotator (VIA)" target="_blank">VIA</a>'
  this.c.appendChild(logo_panel);

  this.c.appendChild(this.via.vm.c);
  this._add_view_manager_tools();

  this._add_spacer();

  this._add_project_tools();

  this._add_spacer();

  this._add_region_shape_selector();

  this._add_spacer();

  var editor = _via_util_get_svg_button('micon_insertcomment', 'Show/Hide Attribute Editor');
  editor.addEventListener('click', function() {
    this.emit_event( 'editor_toggle', {});
  }.bind(this));
  this.c.appendChild(editor);

  this._add_spacer();

  if ( document.getElementById('micon_search') ) {
    var magnifier = _via_util_get_svg_button('micon_search', 'Enable/disable magnifying glass to inspect finer details');
    magnifier.addEventListener('click', function() {
      this.emit_event( 'magnifier_toggle', {}); // control_panel -> view_annotator (bound in _via.js)
    }.bind(this));
    this.c.appendChild(magnifier);

    var fit_screen = _via_util_get_svg_button('micon_fit_screen', 'Fit Image to Screen Height or Width');
    fit_screen.addEventListener('click', function() {
      this.emit_event( 'fit_screen', {}); // control_panel -> view_annotator (bound in _via.js)
    }.bind(this));
    this.c.appendChild(fit_screen);

    var zoomin = _via_util_get_svg_button('micon_zoomin', 'Zoom In');
    zoomin.addEventListener('click', function() {
      this.emit_event( 'zoom_in', {}); // control_panel -> view_annotator (bound in _via.js)
    }.bind(this));
    this.c.appendChild(zoomin);

    var zoomout = _via_util_get_svg_button('micon_zoomout', 'Zoom Out');
    zoomout.addEventListener('click', function() {
      this.emit_event( 'zoom_out', {}); // control_panel -> view_annotator (bound in _via.js)
    }.bind(this));
    this.c.appendChild(zoomout);

    this._add_spacer();
  }

  this._add_project_share_tools();

  this._add_spacer();

  var keyboard = _via_util_get_svg_button('micon_keyboard', 'Keyboard Shortcuts');
  keyboard.addEventListener('click', function() {
    _via_util_page_show('page_keyboard_shortcut');
  }.bind(this));
  this.c.appendChild(keyboard);

  var help = _via_util_get_svg_button('micon_help', 'About VIA');
  help.addEventListener('click', function() {
    _via_util_page_show('page_about');
  }.bind(this));
  this.c.appendChild(help);
}

_via_control_panel.prototype._add_spacer = function() {
  var spacer = document.createElement('div');
  spacer.setAttribute('class', 'spacer');
  this.c.appendChild(spacer);
}

_via_control_panel.prototype._add_view_manager_tools = function() {
  var prev_view = _via_util_get_svg_button('micon_navigate_prev', 'Show Previous File', 'show_prev');
  prev_view.addEventListener('click', this.via.vm._on_prev_view.bind(this.via.vm));
  this.c.appendChild(prev_view);

  var next_view = _via_util_get_svg_button('micon_navigate_next', 'Show Next File', 'show_next');
  next_view.addEventListener('click', this.via.vm._on_next_view.bind(this.via.vm));
  this.c.appendChild(next_view);

  var add_media_local = _via_util_get_svg_button('micon_add_circle', 'Add Audio or Video File in Local Computer', 'add_media_local');
  add_media_local.addEventListener('click', this.via.vm._on_add_media_local.bind(this.via.vm));
  this.c.appendChild(add_media_local);

  var add_media_bulk = _via_util_get_svg_button('micon_lib_add', 'Bulk add file URI ( e.g. file:///... or http://... ) contained in a local CSV file where each row is a remote or local filename.', 'add_media_bulk');
  //add_media_bulk.addEventListener('click', this.via.vm._on_add_media_bulk.bind(this.via.vm));
  add_media_bulk.addEventListener('click', function() {
    var action_map = {
      'via_page_fileuri_button_bulk_add':this._page_on_action_fileuri_bulk_add.bind(this),
    }
    _via_util_page_show('page_fileuri_bulk_add', action_map);
  }.bind(this));
  this.c.appendChild(add_media_bulk);

  var del_view = _via_util_get_svg_button('micon_remove_circle', 'Remove the Current File', 'remove_media');
  del_view.addEventListener('click', this.via.vm._on_del_view.bind(this.via.vm));
  this.c.appendChild(del_view);
}

_via_control_panel.prototype._add_region_shape_selector = function() {
  if ( document.getElementById('shape_point') === null ) {
    return;
  }

  var rect = _via_util_get_svg_button('shape_rectangle', 'Rectangle', 'RECTANGLE');
  rect.addEventListener('click', function() {
    this._set_region_shape('RECTANGLE');
  }.bind(this));
  this.c.appendChild(rect);

  var extreme_rect = _via_util_get_svg_button('shape_extreme_rectangle', 'Extreme rectangle is defined using four points along the boundary of a rectangular object.', 'EXTREME_RECTANGLE');
  extreme_rect.classList.add('shape_selector');
  extreme_rect.addEventListener('click', function() {
    this._set_region_shape('EXTREME_RECTANGLE');
  }.bind(this));
  this.c.appendChild(extreme_rect);

  var circle = _via_util_get_svg_button('shape_circle', 'Circle', 'CIRCLE');
  circle.addEventListener('click', function() {
    this._set_region_shape('CIRCLE');
  }.bind(this));
  this.c.appendChild(circle);

  var extreme_circle = _via_util_get_svg_button('shape_extreme_circle', 'Extreme circle is defined using any three points along the circumference of a circular object.', 'EXTREME_CIRCLE');
  extreme_circle.addEventListener('click', function() {
    this._set_region_shape('EXTREME_CIRCLE');
  }.bind(this));
  this.c.appendChild(extreme_circle);

  var ellipse = _via_util_get_svg_button('shape_ellipse', 'Ellipse', 'ELLIPSE');
  ellipse.addEventListener('click', function() {
    this._set_region_shape('ELLIPSE');
  }.bind(this));
  this.c.appendChild(ellipse);

  var line = _via_util_get_svg_button('shape_line', 'Line', 'LINE');
  line.addEventListener('click', function() {
    this._set_region_shape('LINE');
  }.bind(this));
  this.c.appendChild(line);

  var polygon = _via_util_get_svg_button('shape_polygon', 'Polygon', 'POLYGON');
  polygon.addEventListener('click', function() {
    this._set_region_shape('POLYGON');
  }.bind(this));
  this.c.appendChild(polygon);

  var polyline = _via_util_get_svg_button('shape_polyline', 'Polyline', 'POLYLINE');
  polyline.addEventListener('click', function() {
    this._set_region_shape('POLYLINE');
  }.bind(this));
  this.c.appendChild(polyline);

  var point = _via_util_get_svg_button('shape_point', 'Point', 'POINT');
  point.addEventListener('click', function() {
    this._set_region_shape('POINT');
  }.bind(this));
  this.c.appendChild(point);

  this.shape_selector_list = { 'POINT':point, 'RECTANGLE':rect, 'EXTREME_RECTANGLE':extreme_rect, 'CIRCLE':circle, 'EXTREME_CIRCLE':extreme_circle, 'ELLIPSE':ellipse, 'LINE':line, 'POLYGON':polygon, 'POLYLINE':polyline };
}

_via_control_panel.prototype._set_region_shape = function(shape) {
  this.emit_event( 'region_shape', {'shape':shape});
  for ( var si in this.shape_selector_list ) {
    if ( si === shape ) {
      this.shape_selector_list[si].classList.add('svg_button_selected');
    } else {
      this.shape_selector_list[si].classList.remove('svg_button_selected');
    }
  }
}

_via_control_panel.prototype._add_project_tools = function() {
  var load = _via_util_get_svg_button('micon_open', 'Open a VIA Project');
  load.addEventListener('click', function() {
    _via_util_file_select_local(_VIA_FILE_SELECT_TYPE.JSON, this._project_load_on_local_file_select.bind(this), false);
  }.bind(this));
  this.c.appendChild(load);

  var save = _via_util_get_svg_button('micon_save', 'Save current VIA Project');
  save.addEventListener('click', function() {
    this.via.d.project_save();
  }.bind(this));
  this.c.appendChild(save);

  var import_export_annotation = _via_util_get_svg_button('micon_import_export', 'Import or Export Annotations');
  import_export_annotation.addEventListener('click', this._page_show_import_export.bind(this));
  this.c.appendChild(import_export_annotation);
}

_via_control_panel.prototype._page_show_import_export = function(d) {
  var action_map = {
    'via_page_button_import':this._page_on_action_import.bind(this),
    'via_page_button_export':this._page_on_action_export.bind(this),
  }
  _via_util_page_show('page_import_export', action_map);
}

_via_control_panel.prototype._page_on_action_import = function(d) {
  if ( d._action_id === 'via_page_button_import' ) {
    if ( d.via_page_import_pid !== '' ) {
      this.via.s._project_pull(d.via_page_import_pid).then( function(remote_rev) {
        try {
          var project = JSON.parse(remote_rev);
          // clear remote project identifiers
          project.project.pid = _VIA_PROJECT_ID_MARKER;
          project.project.rev = _VIA_PROJECT_REV_ID_MARKER;
          project.project.rev_timestamp = _VIA_PROJECT_REV_TIMESTAMP_MARKER;
          this.via.d.project_load_json(project);
        }
        catch(e) {
          _via_util_msg_show('Malformed response from server: ' + e);
        }
      }.bind(this), function(err) {
        _via_util_msg_show(err + ': ' + d.via_page_import_pid);
      }.bind(this));
      return;
    }

    if ( d.via_page_import_via2_project_json.length === 1 ) {
      _via_util_load_text_file(d.via_page_import_via2_project_json[0],
                               this._project_import_via2_on_local_file_read.bind(this)
                              );
      return;
    }
    _via_util_msg_show('To import an existing shared project, you must enter its project-id.');
  }
}

_via_control_panel.prototype._page_on_action_export = function(d) {
  if ( d._action_id === 'via_page_button_export' ) {
    this.via.ie.export_to_file(d.via_page_export_format);
  }
}

_via_control_panel.prototype._project_load_on_local_file_select = function(e) {
  if ( e.target.files.length === 1 ) {
    _via_util_load_text_file(e.target.files[0], this._project_load_on_local_file_read.bind(this));
  }
}

_via_control_panel.prototype._project_load_on_local_file_read = function(project_data_str) {
  this.via.d.project_load(project_data_str);
}

_via_control_panel.prototype._project_import_via2_on_local_file_read = function(project_data_str) {
  this.via.d.project_import_via2_json(project_data_str);
}

_via_control_panel.prototype._add_project_share_tools = function() {
  if ( this.via.s ) {
    var share = _via_util_get_svg_button('micon_share', 'Information about sharing this VIA project with others for collaborative annotation');
    share.addEventListener('click', function() {
      this._share_show_info();
    }.bind(this));
    var push = _via_util_get_svg_button('micon_upload', 'Push (i.e. share this project or share your updates made to this project)');
    push.addEventListener('click', function() {
      this.via.s.push();
    }.bind(this));

    var pull = _via_util_get_svg_button('micon_download', 'Pull (i.e. open a shared project or fetch updates for the current project)');
    pull.addEventListener('click', function() {
      this._share_show_pull();
    }.bind(this));

    this.c.appendChild(share);
    this.c.appendChild(push);
    this.c.appendChild(pull);
  }
}

_via_control_panel.prototype._share_show_info = function() {
  if ( this.via.d.project_is_remote() ) {
    this.via.s.exists(this.via.d.store.project.pid).then( function() {
      this.via.s._project_pull(this.via.d.store.project.pid).then( function(ok) {
        try {
          var d = JSON.parse(ok);
          var remote_rev_timestamp = new Date( parseInt(d.project.rev_timestamp) );
          var local_rev_timestamp = new Date( parseInt(this.via.d.store.project.rev_timestamp) );

          var pinfo = '<table>';
          pinfo += '<tr><td>Project Id</td><td>' + d.project.pid + '</td></tr>';
          pinfo += '<tr><td>Remote Revision</td><td>' + d.project.rev + ' (' + remote_rev_timestamp.toUTCString() + ')</td></tr>';
          pinfo += '<tr><td>Local Revision</td><td>' + this.via.d.store.project.rev + ' (' + local_rev_timestamp.toUTCString() + ')</td></tr>';
          pinfo += '</table>';
          if ( d.project.rev !== this.via.d.store.project.rev ) {
            pinfo += '<p>Your version of this project is <span style="color:red;">old</span>. Press <svg class="svg_icon" onclick="" viewbox="0 0 24 24"><use xlink:href="#micon_download"></use></svg> to fetch the most recent version of this project.</p>';
          } else {
            pinfo += '<p>You already have the <span style="color:blue;">latest</span> revision of this project.</p>';
          }

          document.getElementById('via_page_share_project_info').innerHTML = pinfo;
          document.getElementById('via_page_share_project_id').innerHTML = d.project.pid;
          _via_util_page_show('page_share_already_shared');
        }
        catch(e) {
          console.log(e)
          _via_util_msg_show('Malformed server response.' + e);
        }
      }.bind(this), function(pull_err) {
        _via_util_msg_show('Failed to pull project.');
        console.warn(pull_err);
      }.bind(this));
    }.bind(this), function(exists_err) {
      _via_util_page_show('page_share_not_shared_yet');
      console.warn(exists_err);
    }.bind(this));
  } else {
    _via_util_page_show('page_share_not_shared_yet');
  }
}

_via_control_panel.prototype._share_show_pull = function() {
  if ( this.via.d.project_is_remote() ) {
    // check if remote project has newer version
    this.via.s._project_pull(this.via.d.store.project.pid).then( function(ok) {
      try {
        var d = JSON.parse(ok);
        if ( d.project.rev === this.via.d.store.project.rev ) {
          _via_util_msg_show('You already have the latest revision of this project');
          return;
        } else {
          this.via.d.project_merge_rev(d);
        }
      }
      catch(e) {
        _via_util_msg_show('Malformed response from server.');
        console.warn(e);
      }
    }.bind(this), function(err) {
      _via_util_msg_show('Failed to pull project.');
      console.warn(err);
    }.bind(this));
  } else {
    var action_map = {
      'via_page_button_open_shared':this._page_on_action_open_shared.bind(this),
    }
    _via_util_page_show('page_share_open_shared', action_map);
  }
}

_via_control_panel.prototype._page_on_action_open_shared = function(d) {
  if ( d._action_id === 'via_page_button_open_shared' ) {
    this.via.s.pull(d.via_page_input_pid);
  }
}

_via_control_panel.prototype._page_on_action_fileuri_bulk_add = function(d) {
  if ( d.via_page_fileuri_urilist.length ) {
    this.fileuri_bulk_add_from_url_list(d.via_page_fileuri_urilist);
  }

  if ( d.via_page_fileuri_importfile.length === 1 ) {
    switch( parseInt(d.via_page_fileuri_filetype) ) {
    case _VIA_FILE_TYPE.IMAGE:
      _via_util_load_text_file(d.via_page_fileuri_importfile[0], this.fileuri_bulk_add_image_from_file.bind(this));
      break;
    case _VIA_FILE_TYPE.AUDIO:
      _via_util_load_text_file(d.via_page_fileuri_importfile[0], this.fileuri_bulk_add_audio_from_file.bind(this));
      break;
    case _VIA_FILE_TYPE.VIDEO:
      _via_util_load_text_file(d.via_page_fileuri_importfile[0], this.fileuri_bulk_add_video_from_file.bind(this));
      break;
    default:
      _via_util_load_text_file(d.via_page_fileuri_importfile[0], this.fileuri_bulk_add_auto_from_file.bind(this));
    }

  }
}

_via_control_panel.prototype.fileuri_bulk_add_image_from_file = function(uri_list_str) {
  this.fileuri_bulk_add_from_url_list(uri_list_str, _VIA_FILE_TYPE.IMAGE);
}

_via_control_panel.prototype.fileuri_bulk_add_audio_from_file = function(uri_list_str) {
  this.fileuri_bulk_add_from_url_list(uri_list_str, _VIA_FILE_TYPE.AUDIO);
}

_via_control_panel.prototype.fileuri_bulk_add_video_from_file = function(uri_list_str) {
  this.fileuri_bulk_add_from_url_list(uri_list_str, _VIA_FILE_TYPE.VIDEO);
}

_via_control_panel.prototype.fileuri_bulk_add_auto_from_file = function(uri_list_str) {
  this.fileuri_bulk_add_from_url_list(uri_list_str, 0);
}

_via_control_panel.prototype.fileuri_bulk_add_from_url_list = function(uri_list_str, type) {
  var uri_list = uri_list_str.split('\n');
  if ( uri_list.length ) {
    var filelist = [];
    for ( var i = 0; i < uri_list.length; ++i ) {
      if ( uri_list[i] === '' ||
           uri_list[i] === ' ' ||
           uri_list[i] === '\n'
         ) {
        continue; // skip
      }
      var filetype;
      if ( type === 0 || typeof(type) === 'undefined' ) {
        filetype = _via_util_infer_file_type_from_filename(uri_list[i]);
      } else {
        filetype = type;
      }

      filelist.push({ 'fname':uri_list[i],
                      'type':filetype,
                      'loc':_via_util_infer_file_loc_from_filename(uri_list[i]),
                      'src':uri_list[i],
                    });
    }
    this.via.vm._file_add_from_filelist(filelist);
  }
}
</script>
<!-- END: Contents of file: js/_via_control_panel.js-->
<!-- START: Contents of file: js/_via_metadata.js-->
<script>
/**
 *
 * @class
 * @classdesc Metadata
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 31 Dec. 2018
 *
 * @param {Array} z an array of temporal locations (e.g. time, frame index, etc.)
 * @param {Array} xy an array consisting of type and extent of spatial region (e.g. [1, 10, 10, 50, 50] denotes a 50x50 rectangle at (10,10) )
 * @param {Object} an associative array mapping attribute-id to its value
 */

'use strict';

const _VIA_RSHAPE  = { 'POINT':1, 'RECTANGLE':2, 'CIRCLE':3, 'ELLIPSE':4, 'LINE':5, 'POLYLINE':6, 'POLYGON':7, 'EXTREME_RECTANGLE': 8, 'EXTREME_CIRCLE':9 };
const _VIA_METADATA_FLAG = { 'RESERVED_FOR_FUTURE':1 };

function _via_metadata(vid, z, xy, av) {
  this.vid = vid;   // view id
  this.flg = 0;     // flags reserved for future
  this.z   = z;     // [t0, ..., tn] (temporal coordinate e.g. time or frame index)
  this.xy  = xy;    // [shape_id, shape_coordinates, ...] (i.e. spatial coordinate)
  this.av  = av;    // attribute-value pair e.g. {attribute_id : attribute_value, ...}
}
</script>
<!-- END: Contents of file: js/_via_metadata.js-->
<!-- START: Contents of file: js/_via_file.js-->
<script>
/**
 *
 * @class
 * @classdesc Implementation of manual annotator for image, video and audio
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 23 Dec. 2018
 *
 * @param {number} fid a globally unique identifier
 * @param {string} fname filename
 * @param {number} type the file type as defined by _VIA_FILE_TYPE
 * @param {string} loc location of file as defined by _VIA_FILE_LOC
 * @param {string} src URI of the file or base64 data
 */

'use strict'

const _VIA_FILE_TYPE = { IMAGE:2, VIDEO:4, AUDIO:8 };
const _VIA_FILE_LOC  = { LOCAL:1, URIHTTP:2, URIFILE:3, INLINE:4 };

function _via_file(fid, fname, type, loc, src) {
  this.fid      = fid;
  this.fname    = fname;
  this.type     = type;
  this.loc      = loc;
  this.src      = src;
}

</script>
<!-- END: Contents of file: js/_via_file.js-->
<!-- START: Contents of file: js/_via_attribute.js-->
<script>
/**
 *
 * @class
 * @classdesc Attribute
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 31 Dec. 2018
 *
 */

'use strict'

const _VIA_ATTRIBUTE_TYPE = { 'TEXT':1, 'CHECKBOX':2, 'RADIO':3, 'SELECT':4, 'IMAGE':5 };

const _VIA_ATTRIBUTE_ANCHOR = {
  'FILE1_Z0_XY0':'Attribute of a File (e.g. image caption)',
  'FILE1_Z0_XY1':'Spatial Region in an Image (e.g. bounding box of an object)',
  'FILE1_Z0_XYN':'__FUTURE__',   // File region composed of multiple disconnected regions
  'FILE1_Z1_XY0':'__FUTURE__',   // Time marker in video or audio (e.g tongue clicks, speaker diarisation)
  'FILE1_Z1_XY1':'Spatial Region in a Video Frame (e.g. bounding box of an object)',
  'FILE1_Z1_XYN':'__FUTURE__',   // A video frame region composed of multiple disconnected regions
  'FILE1_Z2_XY0':'Temporal Segment in Video or Audio (e.g. video segment containing an actor)',
  'FILE1_Z2_XY1':'__FUTURE__',   // A region defined over a temporal segment
  'FILE1_Z2_XYN':'__FUTURE__',   // A temporal segment with regions defined for start and end frames
  'FILE1_ZN_XY0':'__FUTURE__',   // ? (a possible future use case)
  'FILE1_ZN_XY1':'__FUTURE__',   // ? (a possible future use case)
  'FILE1_ZN_XYN':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z0_XY0':'Attribute of a Group of Files (e.g. given two images, which is more sharp?)',
  'FILEN_Z0_XY1':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z0_XYN':'__FUTURE__',   // one region defined for each file (e.g. an object in multiple views)
  'FILEN_Z1_XY0':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z1_XY1':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z1_XYN':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z2_XY0':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z2_XY1':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_Z2_XYN':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_ZN_XY0':'__FUTURE__',   // one timestamp for each video or audio file (e.g. for alignment)
  'FILEN_ZN_XY1':'__FUTURE__',   // ? (a possible future use case)
  'FILEN_ZN_XYN':'__FUTURE__',   // a region defined in a video frame of each video
};

function _via_attribute(name, anchor_id, type, desc, options, default_option_id) {
  this.aname     = name;
  this.anchor_id = anchor_id;
  this.type      = type;
  this.desc      = desc;
  this.options   = options;
  this.default_option_id = default_option_id;
}

</script>
<!-- END: Contents of file: js/_via_attribute.js-->
<!-- START: Contents of file: js/_via_view.js-->
<script>
/**
 *
 * @class
 * @classdesc A view groups together a set of files and its associated metadata
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 5 Apr. 2019
 *
 */

'use strict'

function _via_view(fid_list) {
  this.fid_list = fid_list; // [ [fid00, fid01, ...], [fid10, fid11, ...] ]
}

</script>
<!-- END: Contents of file: js/_via_view.js-->
<!-- START: Contents of file: js/_via_data.js-->
<script>
/**
 *
 * @class
 * @classdesc Manages the storage and update of all data (annotations, files, etc. )
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 31 Dec. 2018
 *
 */

'use strict';

function _via_data() {
  this._ID = '_via_data_';
  this.store = this._init_default_project();
  this.file_ref = {};        // ref. to files selected using browser's file selector
  this.file_object_uri = {}; // WARNING: cleanup using file_object_url[fid]._destroy_file_object_url()

  this.DATA_FORMAT_VERSION = '3.1.1';

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call(this);
}

_via_data.prototype._init_default_project = function() {
  var p = {};
  p['project'] = {
    'pid': '__VIA_PROJECT_ID__',
    'rev': '__VIA_PROJECT_REV_ID__',
    'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
    'pname': 'Unnamed VIA Project',
    'data_format_version': this.DATA_FORMAT_VERSION,
    'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
    'created': Date.now(),
    'vid_list': [],
  }
  p['config'] = {
    'file': {
      'loc_prefix': { '1':'', '2':'', '3':'', '4':'' }, // constants defined in _via_file._VIA_FILE_LOC
    },
    'ui': {
      'file_content_align':'center',
      'file_metadata_editor_visible':true,
      'spatial_metadata_editor_visible':true,
      'temporal_segment_metadata_editor_visible':true,
      'spatial_region_label_attribute_id':'',
      'gtimeline_visible_row_count':'4',
    },
  };
  p['attribute'] = {};
  p['file'] = {};
  p['metadata'] = {};
  p['view'] = {};

  this.cache = {};
  this.cache['mid_list'] = {};
  this.cache['attribute_group'] = {};

  return p;
}

//
// attribute
//
_via_data.prototype._attribute_get_new_id = function() {
  var aid_list = Object.keys(this.store.attribute).map(Number).sort();
  var n = aid_list.length;
  var aid;
  if ( n ) {
    aid = aid_list[n-1] + 1;
  } else {
    aid = 1;
  }
  return aid;
}

_via_data.prototype._attribute_exist = function(aname) {
  var aid;
  for ( aid in this.store['attribute'] ) {
    if ( this.store['attribute'][aid].aname === aname ) {
      return true;
    }
  }
  return false;
}

_via_data.prototype.attribute_add = function(name, anchor_id, type, desc, options, default_option_id) {
  return new Promise( function(ok_callback, err_callback) {
    if ( this._attribute_exist(name) ) {
      err_callback('attribute already exists');
      return;
    }
    var aid = this._attribute_get_new_id();
    var attr_desc = desc || '';
    var attr_options = options || {};
    var attr_default_option_id = default_option_id || '';
    this.store['attribute'][aid] = new _via_attribute(name,
                                                      anchor_id,
                                                      type,
                                                      attr_desc,
                                                      attr_options,
                                                      attr_default_option_id);
    this._cache_update_attribute_group();
    this.emit_event( 'attribute_add', { 'aid':aid } );
    ok_callback(aid);
  }.bind(this));
}

_via_data.prototype.attribute_del = function(aid) {
  return new Promise( function(ok_callback, err_callback) {
    if ( this._attribute_exist(name) ) {
      err_callback('attribute already exists');
      return;
    }

    // delete all metadata entries for aid
    for ( var mid in this.store.metadata ) {
      if ( this.store.metadata[mid].av.hasOwnProperty(aid) ) {
        delete this.store.metadata[mid].av[aid];
      }
    }

    // delete aid
    delete this.store.attribute[aid];
    this._cache_update_attribute_group();
    this.emit_event( 'attribute_del', { 'aid':aid } );
    ok_callback(aid);
  }.bind(this));
}


_via_data.prototype.attribute_update_anchor_id = function(aid, anchor_id) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.attribute.hasOwnProperty(aid) ) {
      err_callback('aid does not exist');
      return;
    }
    this.store.attribute[aid].anchor_id = anchor_id;
    this._cache_update_attribute_group();
    this.emit_event( 'attribute_update', { 'aid':aid } );
    ok_callback(aid);
  }.bind(this));
}

_via_data.prototype.attribute_anchor_value_to_name = function(anchor_value) {
  for ( var anchor_name in _VIA_ATTRIBUTE_ANCHOR ) {
    if ( _via_util_array_eq(_VIA_ATTRIBUTE_ANCHOR[anchor_name], anchor_value) ) {
      return anchor_name;
    }
  }
  return '';
}

_via_data.prototype.attribute_update_aname = function(aid, new_aname) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.attribute.hasOwnProperty(aid) ) {
      err_callback('aid does not exist');
      return;
    }
    this.store['attribute'][aid]['aname'] = new_aname;
    this.emit_event( 'attribute_update', { 'aid':aid, 'aname':new_aname } );
    ok_callback(aid);
  }.bind(this));
}

_via_data.prototype.attribute_update_type = function(aid, new_type) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.attribute.hasOwnProperty(aid) ) {
      err_callback('aid does not exist');
      return;
    }
    this.store['attribute'][aid]['type'] = new_type;
    this.emit_event( 'attribute_update', { 'aid':aid, 'type':new_type } );
    ok_callback(aid);
  }.bind(this));
}

// option_csv = option1,*default_option,option2,...
_via_data.prototype.attribute_update_options_from_csv = function(aid, options_csv) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.attribute.hasOwnProperty(aid) ) {
      err_callback('aid does not exist');
      return;
    }
    options_csv = options_csv.trim();
    this.store['attribute'][aid]['options'] = {};
    this.store['attribute'][aid]['default_option_id'] = '';
    var options = options_csv.split(',');
    for ( var oid = 0; oid < options.length; ++oid ) {
      var oval = options[oid];
      oval = oval.trim();
      if ( oval.startsWith('*') ) {
        this.store['attribute'][aid]['default_option_id'] = oid.toString();
        oval = oval.substring(1); // remove *
      }
      this.store['attribute'][aid]['options'][oid] = oval;
    }
    this.emit_event( 'attribute_update', { 'aid':aid } );
    ok_callback(aid);
  }.bind(this));
}

//
// file
//
_via_data.prototype._file_get_new_id = function() {
  var max_fid = -Infinity;
  var fid;
  for ( var fid_str in this.store.file ) {
    fid = parseInt(fid_str);
    if ( fid > max_fid ) {
      max_fid = fid;
    }
  }
  if ( max_fid === -Infinity ) {
    return '1';
  } else {
    return (max_fid + 1).toString();
  }
}

_via_data.prototype.file_add = function(name, type, loc, src) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var fid = this._file_get_new_id();
      this.store.file[fid] = new _via_file(fid, name, type, loc, src);
      this.emit_event( 'file_add', { 'fid':fid } );
      ok_callback(fid);
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}


_via_data.prototype.file_update = function(fid, name, value) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( this.store.file.hasOwnProperty(fid) ) {
        if ( name === 'src' ) {
          if ( this.store.file[fid].loc === _VIA_FILE_LOC.LOCAL ) {
            this.file_ref[fid] = value;
            this.store.file[fid]['src'] = '';
          } else {
            this.store.file[fid]['src'] = value;
          }
        } else {
          if ( name === 'loc_prefix' ) {
            this.store.config.file.loc_prefix[this.store.file[fid].loc] = value;
          } else {
            this.store.file[fid][name] = value;
          }
        }
        this.emit_event( 'file_update', { 'fid':fid } );
        ok_callback(fid);
      } else {
        err_callback('fid=' + fid + ' does not exist!');
      }
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.file_get_src = function(fid) {
  if ( this.store.file[fid].loc === _VIA_FILE_LOC.LOCAL ) {
    if(this.file_object_uri.hasOwnProperty(fid)) {
      return this.file_object_uri[fid];
    }

    if(this.file_ref.hasOwnProperty(fid)) {
      this.file_object_uri[fid] = URL.createObjectURL( this.file_ref[fid] );
      return this.file_object_uri[fid];
    } else {
      return '';
    }
  } else {
    return this.store.config.file.loc_prefix[ this.store.file[fid].loc ] + this.store.file[fid].src;
  }
}

_via_data.prototype.file_get_uri = function(fid) {
  if ( this.store.file[fid].loc === _VIA_FILE_LOC.LOCAL ) {
    return this.store.file[fid].fname;
  } else {
    return this.store.config.file.loc_prefix[ this.store.file[fid].loc ] + this.store.file[fid].src;
  }
}

_via_data.prototype.file_free_resources = function(fid) {
  // for files selected using browser's File Selector, we do not
  // need to revoke the ObjectURL because these are simply pointers
  // to the user selected files. If you use revokeObjectURL() to free
  // resources, it will invalidate the pointer to user selected file
  // see https://stackoverflow.com/a/49346614

  //URL.revokeObjectURL( this.file_object_uri[fid] );
  //delete this.file_object_uri[fid];
}

//
// Metadata
//
_via_data.prototype._metadata_get_new_id = function(vid) {
  return vid + '_' + _via_util_uid6();
}

_via_data.prototype.metadata_add = function(vid, z, xy, av) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store['view'].hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      var mid = this._metadata_get_new_id(vid);
      var z_fp = _via_util_float_arr_to_fixed(z, _VIA_FLOAT_FIXED_POINT);
      var xy_fp = _via_util_float_arr_to_fixed(xy, _VIA_FLOAT_FIXED_POINT);
      this.store.metadata[mid] = new _via_metadata(vid, z_fp, xy_fp, av);
      if ( ! this.cache.mid_list.hasOwnProperty(vid) ) {
        this.cache.mid_list[vid] = [];
      }
      this.cache.mid_list[vid].push(mid);

      this.emit_event( 'metadata_add', { 'vid':vid, 'mid':mid } );
      ok_callback( {'vid':vid, 'mid':mid} );
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_add_bulk = function(metadata_list, emit) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var added_mid_list = [];
      for ( var mindex in metadata_list ) {
        var vid = metadata_list[mindex].vid;
        var mid = this._metadata_get_new_id(vid);
        var z_fp = _via_util_float_arr_to_fixed(metadata_list[mindex].z, _VIA_FLOAT_FIXED_POINT);
        var xy_fp = _via_util_float_arr_to_fixed(metadata_list[mindex].xy, _VIA_FLOAT_FIXED_POINT);
        this.store.metadata[mid] = new _via_metadata(vid, z_fp, xy_fp, metadata_list[mindex].av);
        if ( ! this.cache.mid_list.hasOwnProperty(vid) ) {
          this.cache.mid_list[vid] = [];
        }
        this.cache.mid_list[vid].push(mid);
        added_mid_list.push(mid);
      }
      if ( typeof(emit) !== 'undefined' &&
           emit === true ) {
        this.emit_event( 'metadata_add_bulk', { 'mid_list':added_mid_list } );
      }
      ok_callback( { 'mid_list':added_mid_list } );
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_update = function(vid, mid, z, xy, av) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }

      if ( ! this.store.metadata.hasOwnProperty(mid) ) {
        err_callback({'mid':mid});
        return;
      }

      var z_fp = _via_util_float_arr_to_fixed(z, _VIA_FLOAT_FIXED_POINT);
      var xy_fp = _via_util_float_arr_to_fixed(xy, _VIA_FLOAT_FIXED_POINT);
      this.store.metadata[mid] = new _via_metadata(vid, z_fp, xy_fp, av);
      this.emit_event( 'metadata_update', { 'vid':vid, 'mid':mid } );
      ok_callback({'vid':vid, 'mid':mid});
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_update_xy = function(vid, mid, xy) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      if ( ! this.store.metadata.hasOwnProperty(mid) ) {
        err_callback({'mid':mid});
        return;
      }
      var xy_fp = _via_util_float_arr_to_fixed(xy, _VIA_FLOAT_FIXED_POINT);
      this.store.metadata[mid].xy = xy_fp;
      this.emit_event( 'metadata_update', { 'vid':vid, 'mid':mid } );
      ok_callback({'vid':vid, 'mid':mid});
    }
    catch(ex) {
      console.log(xy);
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_update_av = function(vid, mid, aid, avalue) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      if ( ! this.store.metadata.hasOwnProperty(mid) ) {
        err_callback({'mid':mid});
        return;
      }

      this.store.metadata[mid].av[aid] = avalue;
      this.emit_event( 'metadata_update', { 'vid':vid, 'mid':mid } );
      ok_callback({'vid':vid, 'mid':mid});
    }
    catch(ex) {
      console.log(ex);
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_update_av_bulk = function(vid, av_list) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      var updated_mid_list = [];
      var mid, aid, avalue;
      for ( var i in av_list ) {
        mid = av_list[i].mid;
        aid = av_list[i].aid;
        avalue = av_list[i].avalue;
        this.store.metadata[mid].av[aid] = avalue;
        updated_mid_list.push(mid);
      }
      var event_payload = { 'vid':vid, 'mid_list':updated_mid_list };
      this.emit_event('metadata_update_bulk', event_payload);
      ok_callback(event_payload);
    }
    catch(ex) {
      console.log(ex);
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_update_z = function(vid, mid, z) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.view.hasOwnProperty(vid) ) {
      err_callback({'vid':vid});
      return;
    }
    if ( ! this.store.metadata.hasOwnProperty(mid) ) {
      err_callback({'mid':mid});
      return;
    }

    var z_fp = _via_util_float_arr_to_fixed(z, _VIA_FLOAT_FIXED_POINT);
    this.store.metadata[mid].z = z_fp;
    this.emit_event( 'metadata_update', { 'vid':vid, 'mid':mid } );
    ok_callback({'vid':vid, 'mid':mid});
  }.bind(this));
}

_via_data.prototype.metadata_update_zi = function(vid, mid, zindex, zvalue) {
  return new Promise( function(ok_callback, err_callback) {
    if ( ! this.store.view.hasOwnProperty(vid) ) {
      err_callback({'vid':vid});
      return;
    }
    if ( ! this.store.metadata.hasOwnProperty(mid) ) {
      err_callback({'mid':mid});
      return;
    }

    var zvalue_fp = _via_util_float_to_fixed(zvalue, _VIA_FLOAT_FIXED_POINT);
    this.store.metadata[mid].z[zindex] = zvalue_fp;
    this.emit_event( 'metadata_update', { 'vid':vid, 'mid':mid } );
    ok_callback({'vid':vid, 'mid':mid});
  }.bind(this));
}


_via_data.prototype.metadata_delete = function(vid, mid) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      if ( ! this.store.metadata.hasOwnProperty(mid) ) {
        err_callback({'mid':mid});
        return;
      }

      this._cache_mid_list_del(vid, [mid]);
      delete this.store.metadata[mid];
      this.emit_event( 'metadata_delete', { 'vid':vid, 'mid':mid } );
      ok_callback({'vid':vid, 'mid':mid});
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_delete_bulk = function(vid, mid_list, emit) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      var deleted_mid_list = [];
      var mid, cindex;
      for ( var mindex in mid_list ) {
        mid = mid_list[mindex];
        delete this.store.metadata[mid];
        deleted_mid_list.push(mid);
      }
      this._cache_mid_list_del(vid, deleted_mid_list);
      if ( typeof(emit) !== 'undefined' &&
           emit === true ) {
        this.emit_event( 'metadata_delete_bulk', { 'vid':vid, 'mid_list':deleted_mid_list } );
      }
      ok_callback({'vid':vid, 'mid_list':deleted_mid_list});
    }
    catch(ex) {
      console.log(ex);
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.metadata_delete_all = function(vid, emit) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback({'vid':vid});
        return;
      }
      var deleted_mid_list = [];
      var mid, cindex;
      for ( var mid in this.store.metadata ) {
        delete this.store.metadata[mid];
        deleted_mid_list.push(mid);
      }
      this._cache_mid_list_del(vid, deleted_mid_list);
      if ( typeof(emit) !== 'undefined' &&
           emit === true ) {
        this.emit_event( 'metadata_delete_all', { 'vid':vid, 'mid_list':deleted_mid_list } );
      }
      ok_callback({'vid':vid, 'mid_list':deleted_mid_list});
    }
    catch(ex) {
      console.log(ex);
      err_callback(ex);
    }
  }.bind(this));
}

//
// View
//
_via_data.prototype._view_get_new_id = function() {
  var max_vid = -Infinity;
  var vid;
  for ( var vid_str in this.store.view ) {
    vid = parseInt(vid_str);
    if ( vid > max_vid ) {
      max_vid = vid;
    }
  }
  if ( max_vid === -Infinity ) {
    return '1';
  } else {
    return (max_vid + 1).toString();
  }
}

_via_data.prototype.view_add = function(fid_list) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var vid = this._view_get_new_id();
      this.store.view[vid] = new _via_view(fid_list);
      this.store.project.vid_list.push(vid);
      this.emit_event( 'view_add', { 'vid':vid } );
      ok_callback(vid);
    }
    catch(ex) {
      console.log(ex)
      err_callback(ex);
    }
  }.bind(this));
}

_via_data.prototype.view_get_file_vid = function(fid) {
  for ( var vid in this.store.view ) {
    if ( _via_util_array_eq(this.store.view[vid].fid_list, [fid]) ) {
      return vid;
    }
  }
  return -1;
}

// add view with single file
_via_data.prototype.view_bulk_add_from_filelist = function(filelist) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var added_fid_list = [];
      var added_vid_list = [];
      for ( var i = 0; i < filelist.length; ++i ) {
        var fid = this._file_get_new_id();
        if ( filelist[i].loc === _VIA_FILE_LOC.LOCAL ) {
          this.file_ref[fid] = filelist[i].src; // local file ref. stored separately
          filelist[i].src = '';                 // no need to store duplicate of file ref.
        }
        this.store.file[fid] = new _via_file(fid,
                                             filelist[i].fname,
                                             filelist[i].type,
                                             filelist[i].loc,
                                             filelist[i].src);

        var vid = this._view_get_new_id();
        this.store.view[vid] = new _via_view( [ fid ] ); // view with single file
        this.store.project.vid_list.push(vid);

        added_fid_list.push(fid);
        added_vid_list.push(vid);
      }
      var payload = { 'vid_list':added_vid_list, 'fid_list':added_fid_list };
      this.emit_event( 'view_bulk_add', payload );
      ok_callback(payload);
    }
    catch(err) {
      err_callback(err);
    }
  }.bind(this));
}

_via_data.prototype.view_del = function(vid) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      if ( ! this.store.view.hasOwnProperty(vid) ) {
        err_callback();
        return;
      }
      var vindex = this.store.project.vid_list.indexOf(vid);
      if ( vindex === -1 ) {
        err_callback();
        return;
      }

      // delete all metadata
      var mid;
      for ( var mindex in this.cache.mid_list[vid] ) {
        mid = this.cache.mid_list[vid][mindex];
        delete this.store.metadata[mid];
      }
      // delete all files
      var fid;
      for ( var findex in this.store.view[vid].fid_list ) {
        fid = this.store.view[vid].fid_list[findex];
        delete this.store.file[fid];
      }
      // delete view
      delete this.store.view[vid];
      this.store.project.vid_list.splice(vindex, 1);

      this._cache_update_mid_list();
      this.emit_event( 'view_del', {'vid':vid, 'vindex':vindex} );
      ok_callback({'vid':vid, 'vindex':vindex});
    }
    catch(err) {
      err_callback(err);
    }
  }.bind(this));
}

//
// cache
//
_via_data.prototype._cache_update = function() {
  this._cache_update_mid_list();
  this._cache_update_attribute_group();
}

_via_data.prototype._cache_mid_list_del = function(vid, del_mid_list) {
  var mid;
  for ( var mindex in del_mid_list ) {
    mid = del_mid_list[mindex];
    var cindex = this.cache.mid_list[vid].indexOf(mid);
    if ( cindex !== -1 ) {
      this.cache.mid_list[vid].splice(cindex, 1);
    }
  }
}

_via_data.prototype._cache_update_mid_list = function() {
  var vid;
  this.cache.mid_list = {};
  for ( var mid in this.store.metadata ) {
    vid = this.store.metadata[mid].vid;
    if ( ! this.cache.mid_list.hasOwnProperty(vid) ) {
      this.cache.mid_list[vid] = [];
    }
    this.cache.mid_list[vid].push(mid);
  }
}

_via_data.prototype._cache_update_attribute_group = function() {
  this.cache.attribute_group = {};
  var anchor_id;
  for ( var aid in this.store.attribute ) {
    anchor_id = this.store.attribute[aid].anchor_id;
    if ( ! this.cache.attribute_group.hasOwnProperty(anchor_id) ) {
      this.cache.attribute_group[anchor_id] = [];
    }
    this.cache.attribute_group[anchor_id].push(aid);
  }
}

_via_data.prototype._cache_get_attribute_group = function(anchor_id_list) {
  var aid_list = [];
  for ( var i in anchor_id_list ) {
    var anchor_id = anchor_id_list[i];
    if ( this.cache.attribute_group.hasOwnProperty(anchor_id) ) {
      aid_list = aid_list.concat( this.cache.attribute_group[anchor_id] );
    }
  }
  return aid_list;
}

//
// project
//
_via_data.prototype.project_save = function() {
  return new Promise( function(ok_callback, err_callback) {
    try {
      // @todo: decide on whether we want to include the base64 data
      // of inline files (i.e. this.store.file[fid].loc === _VIA_FILE_LOC.INLINE)
      var data_blob = new Blob( [JSON.stringify(this.store)],
                                {type: 'text/json;charset=utf-8'});
      var filename = [];
      if ( this.store.project.pid === _VIA_PROJECT_ID_MARKER ) {
        filename.push('via_project_');
      } else {
        filename.push(this.store.project.pid.substr(0,8) + '_');
      }
      filename.push(_via_util_date_to_filename_str(Date.now()));
      filename.push('.json');
      _via_util_download_as_file(data_blob, filename.join(''));
      ok_callback();
    }
    catch(err) {
      _via_util_msg_show('Failed to save project! [' + err + ']');
      err_callback();
    }
  }.bind(this));
}

_via_data.prototype.project_load = function(project_data_str) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var project_json_data = JSON.parse(project_data_str);
      this.project_load_json(project_json_data).then( function(ok) {
        ok_callback();
      }.bind(this), function(err) {
        err_callback();
      }.bind(this));
    }
    catch(err) {
      _via_util_msg_show('Failed to load project! [' + err + ']');
      this._init_default_project();
      console.log(err)
      err_callback();
    }
  }.bind(this));
}

_via_data.prototype.project_load_json = function(project_json_data) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var project_data = Object.assign({}, project_json_data);
      this.store = this.project_store_apply_version_fix(project_data);
      this.store0 = JSON.parse(JSON.stringify(this.store)); // helps keep track of local changes
      this._cache_update();
      this.emit_event( 'project_loaded', { 'pid':this.store.project.pid } );
      ok_callback();
    }
    catch(err) {
      _via_util_msg_show('Failed to load project! [' + err + ']');
      this._init_default_project();
      console.warn('failed to load project')
      console.log(err)
      err_callback();
    }
  }.bind(this));
}

_via_data.prototype.project_store_apply_version_fix = function(d) {
  switch(d['project']['data_format_version']) {
  case '3.1.0':
    var local_prefix = d['config']['file']['path'];
    delete d['config']['file']['path'];
    d['config']['file']['loc_prefix'] = { '1':'', '2':'', '3':'', '4':'' };
    d['config']['file']['loc_prefix'][_VIA_FILE_LOC.LOCAL] = local_prefix;
    d['project']['data_format_version'] = this.DATA_FORMAT_VERSION;
    d['project']['vid_list'] = d['vid_list'];
    delete d['vid_list'];
    return d;
    break;
  default:
    return d;
    break;
  }
}

_via_data.prototype.project_is_remote = function() {
  if ( this.store.project.pid === _VIA_PROJECT_ID_MARKER &&
       this.store.project.rev === _VIA_PROJECT_REV_ID_MARKER &&
       this.store.project.rev_timestamp === _VIA_PROJECT_REV_TIMESTAMP_MARKER
     ) {
    return false;
  } else {
    return true;
  }
}

//
// merge
//
_via_data.prototype.project_merge_rev = function(remote, merge_strategy) {
  if ( typeof(merge_strategy) === 'undefined' ) {
    merge_strategy = _VIA_MERGE_STRATEGY.THREE_WAY;
  }

  switch(merge_strategy) {
  case _VIA_MERGE_STRATEGY.THREE_WAY:
    this.project_merge_three_way(remote).then( function(ok) {
      console.log('Merge success');
    }.bind(this), function(err) {
      console.log('Merge failed');
    }.bind(this));
    break;
  default:
    console.log('Unknown merge strategy: ' + merge_strategy);
  }
}

_via_data.prototype.project_merge_three_way = function(remote) {
  return new Promise( function(ok_callback, err_callback) {
    // see https://en.wikipedia.org/wiki/Merge_(version_control)
    // merge using the three way merge algorithm, where
    // common ancestor = this.store0 (i.e. the last revision)
    // remote          = the latest revision pulled from server
    // local           = this.store (i.e. local version of project with some revisions)

    if ( this.store.project.pid !== remote.project.pid ) {
      err_callback('pid mismatch');
      return;
    }

    try {
      // merge project
      this.store.project['pname'] = this.merge_3way(this.store0.project['pname'],
                                                    remote.project['pname'],
                                                    this.store.project['pname']);
      this.store.project['vid_list'] = this.merge_3way(this.store0.project['vid_list'],
                                                       remote.project['vid_list'],
                                                       this.store.project['vid_list']);

      // merge remote attribute, file, view and metadata
      var property_list = [ 'config', 'attribute', 'file', 'view', 'metadata' ];
      for ( var pindex in property_list ) {
        var property = property_list[pindex]
        for ( var id in remote[property] ) {
          if ( this.store[property].hasOwnProperty(id) ) {
            for ( var afield in this.store[property][id] ) {
              this.store[property][id][afield] = this.merge_3way(this.store0[property][id][afield],
                                                                 remote[property][id][afield],
                                                                 this.store[property][id][afield]);
            }
          } else {
            // add new attribute
            this.store[property][id] = remote[property][id];
          }
        }
      }

      // check for data that was deleted in remote
      for ( var pindex in property_list ) {
        var property = property_list[pindex]
        for ( var id in this.store0[property] ) {
          if(!remote[property].hasOwnProperty(id)) {
            // something was deleted in remote, therefore we remove it from local version
            delete this.store[property][id];
          }
        }
      }

      this.store.project.rev = remote.project.rev;
      this.store.project.rev_timestamp = remote.project.rev_timestamp;
      this.store0 = JSON.parse(JSON.stringify(remote)); // update our copy of latest remote revision
      this.project_merge_on_success();
    }
    catch (e) {
      _via_util_msg_show('Merge failed: ' + e, true);
      console.warn(e);
      err_callback(e);
    }
  }.bind(this));
}

_via_data.prototype.merge_3way = function(common_ancestor, remote, local) {
  if ( typeof(common_ancestor) === 'object' ) {
    if ( Array.isArray(common_ancestor) ) {
      // use array comparison
      if ( _via_util_array_eq(remote, local) ) {
        return local;
      } else {
        if ( _via_util_array_eq(common_ancestor, local) ) {
          return remote;
        } else {
          return local; // for conflicts, preserve my local updates
        }
      }
    } else {
      // use object comparison
      if ( JSON.stringify(remote) === JSON.stringify(local) ) {
        return local;
      } else {
        if ( JSON.stringify(common_ancestor) === JSON.stringify(local) ) {
          return remote;
        } else {
          return local; // for conflicts, preserve my local updates
        }
      }
    }
  } else {
    if ( remote === local ) {
      return local;
    } else {
      if ( common_ancestor === local ) {
        return remote;
      } else {
        return local; // for conflicts, preserve my local updates
      }
    }
  }
}

_via_data.prototype.project_merge_on_success = function() {
  this._cache_update();
  this.emit_event( 'project_updated', { 'pid':this.store.project.pid } );

  _via_util_msg_show('Successfully merged with revision ' + this.store.project.rev, true);
}

// is there any difference between local project and remote project?
_via_data.prototype.project_is_different = function(others_str) {
  return new Promise( function(ok_callback, err_callback) {
    var ours_str = JSON.stringify(this.store);
    if ( ours_str === others_str ) {
      err_callback();
    } else {
      ok_callback(others_str);
    }
  }.bind(this));
}

//
// Import
//
_via_data.prototype.project_import_via2_json = function(via2_project_json) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var via2 = JSON.parse(via2_project_json);
      this.store = this._init_default_project();

      // add project data
      if ( via2.hasOwnProperty('_via_settings') ) {
        this.store.project.pname = via2['_via_settings']['project']['name'];
        this.store.config.file.loc_prefix[_VIA_FILE_LOC.LOCAL] = via2['_via_settings']['core']['default_filepath'];
      }

      // add attributes
      var metadata_type_anchor_id_map = { 'region':'FILE1_Z0_XY1', 'file':'FILE1_Z0_XY0' };
      var via2_aid_to_via3_aid_map = {};
      for ( var metadata_type in metadata_type_anchor_id_map ) {
        var anchor_id = metadata_type_anchor_id_map[metadata_type];
        for ( var aid in via2['_via_attributes'][metadata_type] ) {
          var options = via2['_via_attributes'][metadata_type][aid]['options'];
          var default_option_id = [];
          for ( var default_oid in via2['_via_attributes'][metadata_type][aid]['default_options'] ) {
            default_option_id.push(default_oid);
          }
          var via3_aid = this._attribute_get_new_id();
          var atype_text = via2['_via_attributes'][metadata_type][aid]['type'].toUpperCase();
          if ( atype_text === 'DROPDOWN' ) {
            atype_text = 'SELECT';
          }
          var atype = _VIA_ATTRIBUTE_TYPE[atype_text];
          this.store['attribute'][via3_aid] = new _via_attribute(aid,
                                                                  anchor_id,
                                                                  atype,
                                                                  via2['_via_attributes'][metadata_type][aid]['description'],
                                                                  options,
                                                                 default_option_id.join(',')
                                                                 );
          via2_aid_to_via3_aid_map[aid] = via3_aid;
        }
      }

      // add file, view and metadata
      for ( var fid in via2['_via_img_metadata'] ) {
        var fname = via2['_via_img_metadata'][fid]['filename'];
        var floc = _via_util_infer_file_loc_from_filename(fname);
        var ftype = _via_util_infer_file_type_from_filename(fname);
        var via3_fid = this._file_get_new_id();
        this.store.file[via3_fid] = new _via_file(via3_fid, fname, ftype, floc, fname);

        var vid = this._view_get_new_id();
        this.store.view[vid] = new _via_view([via3_fid]);
        this.store.project.vid_list.push(vid);

        // import region metadata
        for ( var rid in via2['_via_img_metadata'][fid]['regions'] ) {
          var shape = via2['_via_img_metadata'][fid]['regions'][rid]['shape_attributes'];
          var z = [];
          var xy = [];
          var av = {};
          switch(shape['name']) {
          case 'rect':
            xy = [ _VIA_RSHAPE.RECTANGLE, shape['x'], shape['y'], shape['width'], shape['height'] ];
            break;
          case 'circle':
            xy = [ _VIA_RSHAPE.CIRCLE, shape['cx'], shape['cy'], shape['r'] ];
            break;
          case 'ellipse':
            xy = [ _VIA_RSHAPE.ELLIPSE, shape['cx'], shape['cy'], shape['rx'], shape['ry'] ];
            break;
          case 'point':
            xy = [ _VIA_RSHAPE.POINT, shape['cx'], shape['cy'] ];
            break;
          case 'polygon':
          case 'polyline':
            if ( shape['name'] === 'polygon' ) {
              xy[0] = _VIA_RSHAPE.POLYGON;
            } else {
              xy[0] = _VIA_RSHAPE.POLYLINE;
            }
            var n = shape['all_points_x'].length;
            for ( var i = 0; i < n; ++i ) {
              xy.push(shape['all_points_x'][i], shape['all_points_y'][i]);
            }
            break;
          default:
            console.log('Unknown shape ' + shape['name'] + ' in input file!');
          }

          var region = via2['_via_img_metadata'][fid]['regions'][rid]['region_attributes'];
          for ( var via2_aid in via2['_via_img_metadata'][fid]['regions'][rid]['region_attributes'] ) {
            var via3_aid = via2_aid_to_via3_aid_map[via2_aid];
            var avalue = via2['_via_img_metadata'][fid]['regions'][rid]['region_attributes'][via2_aid];

            if ( typeof(avalue) === 'object' ) {
              avalue = Object.keys(avalue).join(',');
            }

            av[via3_aid] = avalue;
          }

          var mid = this._metadata_get_new_id(vid);
          this.store.metadata[mid] = new _via_metadata(vid, z, xy, av);
        }

        // import file metadata
        var file_av = {};
        for ( var via2_aid in via2['_via_img_metadata'][fid]['file_attributes'] ) {
          var via3_aid = via2_aid_to_via3_aid_map[via2_aid];
          var avalue = via2['_via_img_metadata'][fid]['file_attributes'][via2_aid];

          if ( typeof(avalue) === 'object' ) {
            avalue = Object.keys(avalue).join(',');
          }

          file_av[via3_aid] = avalue;
        }
        var mid = this._metadata_get_new_id(vid);
        this.store.metadata[mid] = new _via_metadata(vid, [], [], file_av);
      }
      _via_util_msg_show('Project import successful');
      this._cache_update();
      this.emit_event( 'project_loaded', {} );
      ok_callback();
    }
    catch(ex) {
      console.log(ex)
      _via_util_msg_show('Failed to import project');
      err_callback(ex);
    }
  }.bind(this));
}
</script>
<!-- END: Contents of file: js/_via_data.js-->
<!-- START: Contents of file: js/_via_share.js-->
<script>
/**
 *
 * @class
 * @classdesc Manages communication with VIA project server to allow sharing of VIA projects.
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 24 June 2019
 *
 */

'use strict';

function _via_share(data, conf) {
  this._ID = '_via_share_';
  this.d = data;
  this.conf = conf;

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call(this);
}

_via_share.prototype._disable_share = function() {
  this.push   = this._disabled_info;
  this.pull   = this._disabled_info;
  this.exists = this._disabled_info;
}

_via_share.prototype._disabled_info = function() {
  _via_util_msg_show('Share feature has been disabled in demo applications!');
}

_via_share.prototype.push = function() {
  if ( this.d.store.project.pid === _VIA_PROJECT_ID_MARKER &&
       this.d.store.project.rev === _VIA_PROJECT_REV_ID_MARKER &&
       this.d.store.project.rev_timestamp === _VIA_PROJECT_REV_TIMESTAMP_MARKER
     ) {
    // avoid pushing empty projects
    if ( Object.keys(this.d.store.file).length === 0 ||
         Object.keys(this.d.store.view).length === 0
       ) {
      _via_util_msg_show('Cannot push empty project');
      return;
    }

    // create a new project
    _via_util_msg_show('Initializing new shared project ...', true);
    this._project_push().then( function(ok) {
      this._project_on_push_ok_response(ok);
    }.bind(this), function(err) {
      this._project_on_push_err_response(err);
    }.bind(this));
  } else {
    // update existing project
    _via_util_msg_show('Checking for updates to remote project ...', true);
    this._project_pull(this.d.store.project.pid).then( function(remote_rev) {
      this.d.project_is_different(remote_rev).then( function(yes) {
        _via_util_msg_show('Checking for updates to remote project ...', true);
        try {
          var d = JSON.parse(yes);
          if ( this.d.store.project.rev === d.project.rev ) {
            // push new revision
            var pid = this.d.store.project.pid;
            var rev = this.d.store.project.rev;
            _via_util_msg_show('Pushing project ...', true);
            this._project_push(pid, rev).then( function(ok) {
              this._project_on_push_ok_response(ok);
            }.bind(this), function(err) {
              this._project_on_push_err_response(err);
            }.bind(this));
          } else {
            // newer revision exists, pull first
            _via_util_msg_show('You must first pull remote revision first. (local revision=' + this.d.store.project.rev + ', remote rev=' + d['project']['rev'] + ')', true);
            return;
          }
        }
        catch(e) {
          _via_util_msg_show('Error parsing response from server: ' + e);
        }
      }.bind(this), function(no) {
        _via_util_msg_show('There are no new changes to push!');
      }.bind(this));
    }.bind(this), function(err) {
      _via_util_msg_show('Failed to retrive remote VIA project: ' + err);
    }.bind(this));
  }
}

_via_share.prototype.pull = function(pid) {
  this._project_pull(pid).then( function(remote_rev) {
    this.d.project_load(remote_rev).then( function() {
      _via_util_msg_show('Loaded shared project ' + pid);
    }.bind(this), function(err) {
      console.warn(err)
      _via_util_msg_show('Failed to load shared project: ' + err);
    }.bind(this));
  }.bind(this), function(err_msg) {
    _via_util_msg_show(err_msg + ' fetching remote shared project: ' + pid);
  }.bind(this));
}

_via_share.prototype.exists = function(pid) {
  return new Promise( function(ok_callback, err_callback) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('load', function() {
      switch(xhr.statusText) {
      case 'OK':
        ok_callback(pid);
        break;
      default:
        err_callback(pid, xhr.statusText);
      }
    });
    xhr.addEventListener('timeout', function(e) {
      err_callback(pid, 'timeout');
    });
    xhr.addEventListener('error', function(e) {
      err_callback(pid, 'error')
    });
    xhr.open('HEAD', this.conf['ENDPOINT'] + pid);
    xhr.send();
  }.bind(this));
}

_via_share.prototype._project_on_push_ok_response = function(ok_response) {
  try {
    var d = JSON.parse(ok_response);
    if ( d.hasOwnProperty('pid') &&
         d.hasOwnProperty('rev') &&
         d.hasOwnProperty('rev_timestamp')
       ) {
      this.d.store.project.pid = d['pid'];
      this.d.store.project.rev = d['rev'];
      this.d.store.project.rev_timestamp = d['rev_timestamp'];
      this.d.store0 = JSON.parse(JSON.stringify(this.d.store)); // helps keep track of local changes
      _via_util_msg_show('Pushed revision ' + d['rev']);
    } else {
      _via_util_msg_show('Malformed response from server: ' + ok);
    }
  }
  catch(e) {
    _via_util_msg_show('Malformed response from server: ' + ok_response);
  }
}

_via_share.prototype._project_on_push_err_response = function(reason, err_msg) {
  _via_util_msg_show('Push failed: ' + reason + ' ' + err_msg);
  console.warn(err_response);
}

_via_share.prototype._project_pull = function(pid) {
  return new Promise( function(ok_callback, err_callback) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('load', function() {
      switch(xhr.statusText) {
      case 'OK':
        ok_callback(xhr.responseText);
        break;
      default:
        err_callback(xhr.statusText);
      }
    });
    xhr.addEventListener('timeout', function(e) {
      err_callback('Timeout');
    });
    xhr.addEventListener('error', function(e) {
      err_callback('Error' )
    });
    xhr.open('GET', this.conf['ENDPOINT'] + pid);
    xhr.send();
  }.bind(this));
}

_via_share.prototype._project_push = function(pid, rev) {
  return new Promise( function(ok_callback, err_callback) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('load', function() {
      switch(xhr.statusText) {
      case 'OK':
        ok_callback(xhr.responseText);
        break;
      default:
        err_callback(xhr.statusText);
      }
    });
    xhr.addEventListener('timeout', function(e) {
      console.log('timeout');
      err_callback(pid, 'timeout');
    });
    xhr.addEventListener('error', function(e) {
      console.log(e.target)
      err_callback(pid, 'error')
    });

    var payload = JSON.parse(JSON.stringify(this.d.store));
    payload.project.rev = _VIA_PROJECT_REV_ID_MARKER;
    payload.project.rev_timestamp = _VIA_PROJECT_REV_TIMESTAMP_MARKER;
    if ( typeof(pid) === 'undefined' &&
         typeof(rev) === 'undefined'
       ) {
      payload.project.pid = _VIA_PROJECT_ID_MARKER;
      xhr.open('POST', this.conf['ENDPOINT']);
      console.log('POST ' + this.conf['ENDPOINT'])
    } else {
      xhr.open('POST', this.conf['ENDPOINT'] + pid + '?rev=' + rev);
    }
    xhr.timeout = _VIA_REMOTE_TIMEOUT;
    xhr.send(JSON.stringify(payload));
  }.bind(this));
}
</script>
<!-- END: Contents of file: js/_via_share.js-->
<!-- START: Contents of file: js/_via_import_export.js-->
<script>
/**
 *
 * @class
 * @classdesc Manages import and export of annotations
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 26 May 2019
 *
 */

'use strict';

function _via_import_export(data) {
  this.d = data;

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  this._EVENT_ID_PREFIX = '_via_import_export_';
  _via_event.call(this);
}

_via_import_export.prototype.import_from_file = function(data_format, file) {
  switch(data_format) {
  case 'coco':
    _via_util_load_text_file(file[0], this.import_from_coco.bind(this));
    break;
  case 'webvtt':
    _via_util_load_text_file(file[0], this.import_from_webvtt.bind(this));
  default:
    console.warn('Unknown data format: ' + data_format);
  }
}

_via_import_export.prototype.import_from_coco = function(json_str) {
  try {
    var d = JSON.parse(json_str);
    if ( ! ( d.hasOwnProperty('annotations') &&
             d.hasOwnProperty('categories') &&
             d.hasOwnProperty('images')
           )
       ) {
      _via_util_msg_show('Cannot import annotations from malformed JSON!');
      return;
    }
    var p = this.d._init_default_project();

    // add files
    for ( var i in d.images ) {
      var fid = d.images[i].id;
      var fname = d.images[i].file_name;
      var src = d.images[i].coco_url;
      var type = _VIA_FILE_TYPE.IMAGE;
      var loc = _VIA_FILE_LOC.URIHTTP;
      p.file[fid] = new _via_file(fid, fname, type, loc, src);

      // add a view for each file
      var vid = fid;
      p.view[vid] = new _via_view( [ fid ] ); // view with single file
      p.project.vid_list.push(vid);
    }

    // add attributes
    for ( var i in d.categories ) {
      var aid = d.categories[i].id;
      var aname = d.categories[i].name;
      p.attribute[aid] = new _via_attribute(aname, 'FILE1_Z0_XY1', _VIA_ATTRIBUTE_TYPE.TEXT, {}, {});
/*
      var aid = d.categories[i].supercategory;
      var option_id = d.categories[i].id;
      var option_value = d.categories[i].name;

      if ( p.attribute.hasOwnProperty(aid) ) {
        // add as an option
        p.attribute[aid].options[option_id] = option_value;
      } else {
        var options = {};
        options[option_id] = option_value;
        p.attribute[aid] = new _via_attribute(aid, 'FILE1_Z0_XY1', _VIA_ATTRIBUTE_TYPE.SELECT, options, {});
      }
*/
    }

    // add metadata
    for ( var i in d.annotations ) {
      var mid = d.annotations[i].id;
      var vid = d.annotations[i].image_id;
      if ( p.file.hasOwnProperty(vid) ) {
        var aid = d.annotations[i].category_id;
        var shape = [_VIA_RSHAPE.POLYGON];
        var av = {}
        av[aid] = p.attribute[aid].aname;

        for ( var j = 0; j < d.annotations[i].segmentation[0].length; ++j ) {
          shape.push(d.annotations[i].segmentation[0][j]);
        }

        p.metadata[mid] = new _via_metadata(vid, [], shape, av);
      }
    }

    this.d.project_load_json(p);
  }
  catch(e) {
    _via_util_msg_show('Failed to import annotations: ' + e);
    console.warn(e)
  }
}

_via_import_export.prototype.export_to_file = function(data_format) {
  console.log(data_format)
  switch(data_format) {
  case 'via3_csv':
    this.export_to_via3_csv();
    break;
  case 'temporal_segments_csv':
    this.export_to_temporal_segments_csv();
    break;
  case 'webvtt':
    this.export_to_webvtt();
    break;

  default:
    console.warn('Unknown data format: ' + data_format);
  }
}

_via_import_export.prototype.export_to_via3_csv = function() {
  return new Promise( function(ok_callback, err_callback) {
    var csv = [];

    var attribute = {}
    for ( var aid in this.d.store.attribute ) {
      attribute[aid] = this.d.store.attribute[aid].aname;
    }

    csv.push('# Exported using VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)');
    csv.push('# Notes:');
    csv.push('# - spatial_coordinates of [2,10,20,50,80] denotes a rectangle (shape_id=2) of size 50x80 placed at (10,20)');
    csv.push('# - temporal coordinate of [1.349,2.741] denotes a temporal segment from time 1.346 sec. to 2.741 sec.');
    csv.push('# - temporal coordinate of [4.633] denotes a still video frame at 4.633 sec.');
    csv.push('# - metadata of {""1"":""3""} indicates attribute with id "1" set to an attribute option with id "3"');

    csv.push('# SHAPE_ID = ' + JSON.stringify(_VIA_RSHAPE));
    csv.push('# FLAG_ID = ' + JSON.stringify(_VIA_METADATA_FLAG));
    csv.push('# ATTRIBUTE = ' + JSON.stringify(this.d.store.attribute));
    csv.push('# CSV_HEADER = metadata_id,file_list,flags,temporal_coordinates,spatial_coordinates,metadata');
    // build file_list for each view_id
    var vid_filesrc_str_list = {};
    var vid, fid;
    for ( var vindex in this.d.store.project.vid_list ) {
      vid = this.d.store.project.vid_list[vindex];
      var vid_filesrc_list = [];
      for ( var findex in this.d.store.view[vid].fid_list ) {
        fid = this.d.store.view[vid].fid_list[findex];
        switch(this.d.store.file[fid].loc) {
        case _VIA_FILE_LOC.LOCAL:
          if ( this.d.file_ref.hasOwnProperty(fid) ) {
            vid_filesrc_list.push( this.d.file_ref[fid].name );
          } else {
            vid_filesrc_list.push( this.d.store.file[fid].fname );
          }
          break;
        case _VIA_FILE_LOC.INLINE:
          vid_filesrc_list.push( this.d.store.file[fid].fname );
          break;
        default:
          vid_filesrc_list.push( this.d.store.file[fid].src );
        }
      }
      vid_filesrc_str_list[vid] = _via_util_obj2csv(vid_filesrc_list);
    }

    for ( var mid in this.d.store.metadata ) {
      var line = [];
      line.push( '"' + mid + '"');
      line.push( vid_filesrc_str_list[ this.d.store.metadata[mid].vid ] );
      line.push(this.d.store.metadata[mid].flg);
      line.push( _via_util_obj2csv(this.d.store.metadata[mid].z) );
      line.push( _via_util_obj2csv(this.d.store.metadata[mid].xy) );
      line.push( _via_util_obj2csv(this.d.store.metadata[mid].av) );
      csv.push(line.join(','));
    }

    var data_blob = new Blob( [csv.join('\n')],
                              {type: 'text/csv;charset=utf-8'});
    var filename = [];
    filename.push(this.d.store.project.pname.replace(' ', '-'));
    filename.push(_via_util_date_to_filename_str(Date.now()));
    filename.push('_export.csv');
    _via_util_download_as_file(data_blob, filename.join(''));
  }.bind(this));
}

_via_import_export.prototype.export_to_temporal_segments_csv = function() {
  return new Promise( function(ok_callback, err_callback) {
    var csv = [];

    var attribute = {}
    for ( var aid in this.d.store.attribute ) {
      attribute[aid] = this.d.store.attribute[aid].aname;
    }

    csv.push('# Exported using VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)');
    csv.push('# CSV_HEADER = metadata_id,file_list,temporal_segment_start,temporal_segment_end,metadata');
    // build file_list for each view_id
    var vid_filesrc_str_list = {};
    var vid, fid;
    for ( var vindex in this.d.store.project.vid_list ) {
      vid = this.d.store.project.vid_list[vindex];
      var vid_filesrc_list = [];
      for ( var findex in this.d.store.view[vid].fid_list ) {
        fid = this.d.store.view[vid].fid_list[findex];
        switch(this.d.store.file[fid].loc) {
        case _VIA_FILE_LOC.LOCAL:
          if ( this.d.file_ref.hasOwnProperty(fid) ) {
            vid_filesrc_list.push( this.d.file_ref[fid].name );
          } else {
            vid_filesrc_list.push( this.d.store.file[fid].fname );
          }
          break;
        case _VIA_FILE_LOC.INLINE:
          vid_filesrc_list.push( this.d.store.file[fid].fname );
          break;
        default:
          vid_filesrc_list.push( this.d.store.file[fid].src );
        }
      }
      vid_filesrc_str_list[vid] = _via_util_obj2csv(vid_filesrc_list);
    }

    for ( var mid in this.d.store.metadata ) {
      var line = [];
      line.push( '"' + mid + '"');
      line.push( vid_filesrc_str_list[ this.d.store.metadata[mid].vid ] );
      line.push(this.d.store.metadata[mid].z[0])
      line.push(this.d.store.metadata[mid].z[1])
      var descriptive_av = {};
      for ( var aid in this.d.store.metadata[mid].av ) {
        descriptive_av[ this.d.store.attribute[aid].aname ] = this.d.store.metadata[mid].av[aid];
      }
      line.push( _via_util_obj2csv(descriptive_av) );
      csv.push(line.join(','));
    }

    var data_blob = new Blob( [csv.join('\n')],
                              {type: 'text/csv;charset=utf-8'});
    var filename = [];
    filename.push(this.d.store.project.pname.replace(' ', '-'));
    filename.push(_via_util_date_to_filename_str(Date.now()));
    filename.push('_export.csv');
    _via_util_download_as_file(data_blob, filename.join(''));
  }.bind(this));
}

_via_import_export.prototype.export_to_webvtt = function() {
  return new Promise( function(ok_callback, err_callback) {
    var csv = [];

    var attribute = {}
    for ( var aid in this.d.store.attribute ) {
      attribute[aid] = this.d.store.attribute[aid].aname;
    }

    csv.push('WEBVTT');
    csv.push('');
    // build file_list for each view_id
    var vid_filesrc_str_list = {};
    var vid, fid;
    for ( var vindex in this.d.store.project.vid_list ) {
      vid = this.d.store.project.vid_list[vindex];
      var vid_filesrc_list = [];
      for ( var findex in this.d.store.view[vid].fid_list ) {
        fid = this.d.store.view[vid].fid_list[findex];
        switch(this.d.store.file[fid].loc) {
        case _VIA_FILE_LOC.LOCAL:
          if ( this.d.file_ref.hasOwnProperty(fid) ) {
            vid_filesrc_list.push( this.d.file_ref[fid].name );
          } else {
            vid_filesrc_list.push( this.d.store.file[fid].fname );
          }
          break;
        case _VIA_FILE_LOC.INLINE:
          vid_filesrc_list.push( this.d.store.file[fid].fname );
          break;
        default:
          vid_filesrc_list.push( this.d.store.file[fid].src );
        }
      }
      vid_filesrc_str_list[vid] = _via_util_obj2csv(vid_filesrc_list);
    }

    for ( var mid in this.d.store.metadata ) {
      var tstart = _via_seconds_to_hh_mm_ss_ms(this.d.store.metadata[mid].z[0]);
      var tstart_str = tstart[0] + ':' + tstart[1] + ':' + tstart[2] + '.' + tstart[3];
      var tend = _via_seconds_to_hh_mm_ss_ms(this.d.store.metadata[mid].z[1]);
      var tend_str = tend[0] + ':' + tend[1] + ':' + tend[2] + '.' + tend[3];
      var subtitle = [];
      for ( var aid in this.d.store.metadata[mid].av ) {
        subtitle.push(this.d.store.metadata[mid].av[aid]);
      }
      var subtitle_str = subtitle.join(' ');
      csv.push(tstart_str + ' --> ' + tend_str);
      csv.push(subtitle.join(' '))
      csv.push('');
    }
    var data_blob = new Blob( [csv.join('\n')],
                              {type: 'text/vtt;charset=utf-8'});
    var filename = [];
    filename.push(this.d.store.project.pname);
    filename.push('.vtt');
    _via_util_download_as_file(data_blob, filename.join(''));
  }.bind(this));
}

_via_import_export.prototype.import_from_webvtt = function(webvtt_str, vid, subtitle_aid) {
  return new Promise( function(ok_callback, err_callback) {
    var line_split_regex = new RegExp('\n|\r|\r\n', 'g');
    var webvtt_lines = webvtt_str.split(line_split_regex);
    var metadata_list = [];
    var webvtt_lines_count = webvtt_lines.length;
    var current_line_index = 0;
    var timestamp_found = false;
    var timestamp_line_index = -1;
    var subtitles_parse_started = false;
    var subtitle_text = [];
    while(current_line_index < webvtt_lines_count) {
      //console.log('Processing line [' + current_line_index + '] : ' + webvtt_lines[current_line_index]);
      if( webvtt_lines[current_line_index] === '' ) {
        // ignore empty lines
        current_line_index += 1;
        //console.log('  ignored empty line');
        continue;
      }
      if( webvtt_lines[current_line_index].includes(' --> ') ) {
        if(timestamp_found) {
          // encountered timestamp for a new subtitle
          // we now process previous subtitle
          // we do not increment current_line_index so that the parser
          // encounters this line again and begins parsing the new subtitle
          var timestamp_tokens = webvtt_lines[timestamp_line_index].split(' --> ');
          var starttime_sec = _via_hh_mm_ss_ms_to_seconds(timestamp_tokens[0]);
          var endtime_sec = _via_hh_mm_ss_ms_to_seconds(timestamp_tokens[1]);
          var metadata = {'vid': vid,
                          'z':[starttime_sec, endtime_sec],
                          'xy':[],
                          'av':{}
                         }
          metadata['av'][subtitle_aid] = subtitle_text.join(' ');
          metadata_list.push(metadata);
          //console.log('  added subtitle: ' + JSON.stringify(metadata))
          timestamp_found = false; // to allow this new timestamp to be processed in next iteration
          timestamp_line_index = -1;
          subtitle_text = [];
        } else {
          timestamp_found = true;
          timestamp_line_index = current_line_index;
          //console.log('  timestamp found at index: ' + timestamp_line_index);
          current_line_index += 1;
        }
        continue;
      } else {
        if(timestamp_found) {
          subtitle_text.push(webvtt_lines[current_line_index]);
          //console.log('  pushed part of subtitle to subtitle text array');
        } else {
          // not an empty line
          // timestamp has not been encountered yet
          // we ignore other irrelevant parts (e.g. header)
          //console.log('  IGNORE: ' + webvtt_lines[current_line_index]);
        }
        current_line_index += 1;
        continue;
      }
    }

    //this.d.metadata_add_bulk(metadata_list, true); // appends to existing metadata

    // removes existing subtitles and adds new subtitles
    this.d.metadata_delete_all(vid, true).then( function(ok) {
      this.d.metadata_add_bulk(metadata_list, true);
    }.bind(this), function(err) {
      console.log(err);
    }.bind(this));
  }.bind(this));
}
</script>
<!-- END: Contents of file: js/_via_import_export.js-->

<!-- START: Contents of file: js/_via_video_thumbnail.js-->
<script>
/**
 * @class
 * @classdesc Extracts thumbnail sized frames from a video
 *
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @since 5 Feb. 2019
 * @param {_via_file} an instance of _via_file corresponding to a video file
 */

'use strict';

function _via_video_thumbnail(fid, data) {
  this._ID = '_via_video_thumbnail_';
  this.fid = fid;
  this.d = data;
  this.fwidth = 160;
  this.file_object_url = undefined; // file contents are in this the object url
  this.frames = {}; // indexed by second

  if ( this.d.store.file[this.fid].type !== _VIA_FILE_TYPE.VIDEO ) {
    console.log('_via_video_thumbnail() : file type must be ' +
                _VIA_FILE_TYPE.VIDEO + ' (got ' + this.d.store.file[this.fid].type + ')');
    return;
  }

  // state
  this.is_thumbnail_read_ongoing = false;
  this.thumbnail_time = 0;
  this.thumbnail_canvas = document.createElement('canvas');

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );

  this._init();
}

_via_video_thumbnail.prototype._init = function() {
}

_via_video_thumbnail.prototype.load = function() {
  return new Promise( function(ok_callback, err_callback) {
    this._load_video().then( function() {
      this.video.currentTime = 0.0;
      ok_callback();
    }.bind(this), function(load_err) {
      console.log(load_err);
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_video_thumbnail.prototype._load_video = function() {
  return new Promise( function(ok_callback, err_callback) {
    this.video = document.createElement('video');
    this.video.setAttribute('src', this.d.file_get_src(this.fid));
    //this.video.setAttribute('autoplay', false);
    //this.video.setAttribute('loop', false);
    //this.video.setAttribute('controls', '');
    this.video.setAttribute('preload', 'auto');
    //this.video.setAttribute('crossorigin', 'anonymous');

    this.video.addEventListener('loadeddata', function() {
      this.d.file_free_resources(this.fid);
      var aspect_ratio = this.video.videoHeight / this.video.videoWidth;
      this.fheight = Math.floor(this.fwidth * aspect_ratio);
      this.thumbnail_canvas.width = this.fwidth;
      this.thumbnail_canvas.height = this.fheight;
      this.thumbnail_context = this.thumbnail_canvas.getContext('2d', { alpha:false });
      ok_callback();
    }.bind(this));
    this.video.addEventListener('error', function() {
      this.d.file_free_resources(this.fid);
      console.log('_via_video_thumnnail._load_video() error')
      err_callback('error');
    }.bind(this));
    this.video.addEventListener('abort', function() {
      this.d.file_free_resources(this.fid);
      console.log('_via_video_thumbnail._load_video() abort')
      err_callback('abort');
    }.bind(this));

    this.video.addEventListener('seeked', this._on_seeked.bind(this));
  }.bind(this));
}

_via_video_thumbnail.prototype.get_thumbnail = function(time_float) {
  this.is_thumbnail_read_ongoing = true;
  this.thumbnail_time = parseInt(time_float);
  this.video.currentTime = this.thumbnail_time;
  return this.thumbnail_canvas;
}

_via_video_thumbnail.prototype._on_seeked = function() {
  if ( this.is_thumbnail_read_ongoing &&
       this.thumbnail_context
     ) {
    this.is_thumbnail_read_ongoing = false;
    this.thumbnail_context.drawImage(this.video,
                                     0, 0, this.video.videoWidth, this.video.videoHeight,
                                     0, 0, this.fwidth, this.fheight
                                    );
  }
}
</script>
<!-- END: Contents of file: js/_via_video_thumbnail.js-->
<!-- START: Contents of file: js/_via_file_annotator.js-->
<script>
/**
 * @class
 * @classdesc Manages region draw and view operations on an image or video
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @since 8 Apr. 2019
 */

'use strict';

var _VIA_RINPUT_STATE = {
  UNKNOWN: 0,
  SUSPEND: 1,
  IDLE: 2,
  REGION_SELECTED: 3,
  REGION_SELECT_OR_DRAW_POSSIBLE: 4,
  SELECT_ALL_INSIDE_AN_AREA_ONGOING: 5,
  REGION_UNSELECT_ONGOING: 6,
  REGION_SELECT_TOGGLE_ONGOING: 7,
  REGION_MOVE_ONGOING: 8,
  REGION_RESIZE_ONGOING: 9,
  REGION_DRAW_ONGOING: 10,
  REGION_DRAW_NCLICK_ONGOING: 11,
};

function _via_file_annotator(view_annotator, data, vid, file_label, container) {
  this._ID = '_via_file_annotator_';
  this.va = view_annotator;
  this.d = data;
  this.vid = vid;
  this.file_label = file_label;
  this.c = container;

  // state variables
  this.state_id = this._state_set(_VIA_RINPUT_STATE.UNKNOWN);
  this.user_input_pts = []; // [x0, y0, x1, y1, ..., xk, yk]
  this.last_clicked_mid_list = [];
  this.resize_control_point_index = -1;
  this.resize_selected_mid_index = -1;
  this.show_region_shape = true;
  this.show_region_label = true;
  
  // canvas regions
  this.creg = {}; // canvas regions
  this.selected_mid_list = [];

  // last known mouse cursor position
  this.last_cx = 0;
  this.last_cy = 0;

  // zoom
  this._is_magnifier_enabled = false;
  this.magnifier_scale = 3.0;
  this.magnifier_scale_index = 6;
  this.magnifier_scale_list = [0.2, 0.4, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0];

  // constants
  this.conf = {};
  this.conf.CONTROL_POINT_RADIUS = 2;
  this.conf.CONTROL_POINT_COLOR  = 'red';
  this.conf.CONTROL_POINT_CLICK_TOL = 3;
  this.conf.REGION_BOUNDARY_COLOR = 'yellow';
  this.conf.REGION_LINE_WIDTH = 2;
  this.conf.SEL_REGION_BOUNDARY_COLOR = 'black';
  this.conf.SEL_REGION_FILL_COLOR = '#808080';
  this.conf.SEL_REGION_FILL_OPACITY = 0.1;
  this.conf.SEL_REGION_LINE_WIDTH = 2;
  this.conf.REGION_POINT_RADIUS = 3;
  this.conf.FIRST_VERTEX_CLICK_TOL = 3;
  this.conf.FIRST_VERTEX_BOUNDARY_WIDTH = 1;
  this.conf.FIRST_VERTEX_BOUNDARY_COLOR = 'black';
  this.conf.FIRST_VERTEX_FILL_COLOR = 'white';
  this.conf.REGION_SMETADATA_MARGIN = 4; // in pixel
  this.conf.FILE_METADATA_MARGIN = 4; // in pixel
  this.conf.CROSSHAIR_COLOR1 = '#1a1a1a';
  this.conf.CROSSHAIR_COLOR2 = '#e6e6e6';
  this.conf.SPATIAL_REGION_TIME_TOL = 0.02; // in sec

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );

  // register event listeners
  this.d.on_event('metadata_add', this._ID, this._on_event_metadata_add.bind(this));
  this.d.on_event('metadata_update', this._ID, this._on_event_metadata_update.bind(this));
  this.d.on_event('metadata_delete_bulk', this._ID, this._on_event_metadata_delete_bulk.bind(this));
  this.d.on_event('view_update', this._ID, this._on_event_view_update.bind(this));
  this.d.on_event('attribute_update', this._ID, this._on_event_attribute_update.bind(this));
  this.d.on_event('attribute_del', this._ID, this._on_event_attribute_del.bind(this));

  this._init();
}

_via_file_annotator.prototype._init = function() {
  if ( this.d.store.view[this.vid].fid_list.length !== 1 ) {
    console.warn('_via_file_annotator() can only operate on a single file!');
    return;
  }

  if ( ! this.d.store.config.ui.hasOwnProperty('file_metadata_editor_visible') ) {
    this.d.store.config.ui['file_metadata_editor_visible'] = true;
  }
  if ( ! this.d.store.config.ui.hasOwnProperty('spatial_metadata_editor_visible') ) {
    this.d.store.config.ui['spatial_metadata_editor_visible'] = true;
  }

  this.fid = this.d.store.view[this.vid].fid_list[0];
}

_via_file_annotator.prototype._magnifier_toggle = function() {
  if(this._is_magnifier_enabled) {
    this.magnifier_container.classList.add('hide');
    this._is_magnifier_enabled = false;
    this.magnifier_container.innerHTML = '';
    _via_util_msg_show('Deactivated magnifying glass.');
  } else {
    this._is_magnifier_enabled = true;
    this._magnifier_activate();
    this.magnifier_container.classList.remove('hide');
    this._magnifier_update_position();
    _via_util_msg_show('Activated magnifying glass to allow finer inspection of feature.');
  }
}

_via_file_annotator.prototype._magnifier_activate = function() {
  this.magnifier_container.innerHTML = '';

  // add filecontent
  var filecontent = this.file_html_element.cloneNode(true);
  filecontent.removeAttribute('style');
  filecontent.removeAttribute('id');

  this.magnifier_canvas_width = this.cwidth * this.magnifier_scale;
  this.magnifier_canvas_height = this.cheight * this.magnifier_scale;
  filecontent.setAttribute('width', this.magnifier_canvas_width);
  filecontent.setAttribute('height', this.magnifier_canvas_height);

  var rshape = document.createElement('canvas');
  rshape.setAttribute('id', 'magnifier_region_shape');
  rshape.width = this.magnifier_canvas_width;
  rshape.height = this.magnifier_canvas_height;
  this.magnifier_rshape_ctx = rshape.getContext('2d');
  this.magnifier_rshape_ctx.drawImage(this.rshape_canvas, 0, 0, this.cwidth, this.cheight,
                                 0, 0, this.magnifier_canvas_width, this.magnifier_canvas_height);

  var tempr = document.createElement('canvas');
  tempr.setAttribute('id', 'magnifier_region_input');
  tempr.width = this.magnifier_canvas_width;
  tempr.height = this.magnifier_canvas_height;
  this.magnifier_tempr_ctx = tempr.getContext('2d');

  this.magnifier_container.appendChild(filecontent);
  this.magnifier_container.appendChild(rshape);
  this.magnifier_container.appendChild(tempr);

  // zoom panel position gets updated by _magnifier_update_position() on mousemove event
  // zoom panel content gets updated by _draw()
}

_via_file_annotator.prototype._magnifier_update_position = function() {
  var magnifier_panel_left = this.left_pad + this.last_cx - this.magnifier_container.offsetWidth/2;
  var magnifier_panel_top  = this.last_cy - this.magnifier_container.offsetHeight/2;

  // position zoom container
  var style = [];
  style.push('top:' + magnifier_panel_top + 'px');
  style.push('left:' + magnifier_panel_left + 'px');
  style.push('border-radius:2em;');
  this.magnifier_container.setAttribute('style', style.join(';'));

  // position filecontent
  style = [];
  style.push('position: absolute');
  var scaled_img_left = this.magnifier_container.offsetWidth/2 - this.last_cx * this.magnifier_scale;
  var scaled_img_top  = this.magnifier_container.offsetHeight/2 - this.last_cy * this.magnifier_scale;
  style.push('top:' + scaled_img_top + 'px');
  style.push('left:' + scaled_img_left + 'px');
  this.magnifier_container.childNodes[0].setAttribute('style', style.join(';'));
  this.magnifier_container.childNodes[1].setAttribute('style', style.join(';'));
  this.magnifier_container.childNodes[2].setAttribute('style', style.join(';'));
}

_via_file_annotator.prototype._file_load_show_error_page = function() {
  this.c.innerHTML = '';
  var page = document.createElement('div');
  page.setAttribute('class', 'error_page');

  var title = document.createElement('h1');
  title.innerHTML = 'File Not Found!';
  page.appendChild(title);

  var msg = document.createElement('p');
  msg.innerHTML = 'File "<code>' + this.d.file_get_uri(this.fid) + '</code>" not found. ';
  msg.innerHTML += 'VIA application will automatically reload this file when you update one of the properties below.';
  page.appendChild(msg);

  var table = document.createElement('table');
  var filename_row = document.createElement('tr');
  var filename_label = document.createElement('td');
  filename_label.innerHTML = 'Filename';
  var filename_cell = document.createElement('td');
  var filename_input = document.createElement('input');
  filename_input.setAttribute('type', 'text');
  filename_input.setAttribute('value', this.d.store.file[this.fid].fname);
  filename_input.setAttribute('data-pname', 'fname');
  filename_input.addEventListener('change', this._file_on_attribute_update.bind(this));
  filename_cell.appendChild(filename_input);
  filename_row.appendChild(filename_label);
  filename_row.appendChild(filename_cell);
  page.appendChild(filename_row);

  var filetype_row = document.createElement('tr');
  var filetype_label = document.createElement('td');
  filetype_label.innerHTML = 'File Type';
  filetype_row.appendChild(filetype_label);
  var filetype_select = document.createElement('select');
  filetype_select.setAttribute('data-pname', 'type');
  filetype_select.addEventListener('change', this._file_on_attribute_update.bind(this));

  for ( var filetype in _VIA_FILE_TYPE ) {
    var oi = document.createElement('option');
    oi.setAttribute('value', _VIA_FILE_TYPE[filetype]);
    oi.innerHTML = filetype;
    if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE[filetype] ) {
      oi.setAttribute('selected', '');
    }
    filetype_select.appendChild(oi);
  }
  var filetype_select_cell = document.createElement('td');
  filetype_select_cell.appendChild(filetype_select);
  filetype_row.appendChild(filetype_select_cell);
  page.appendChild(filetype_row);

  var fileloc_row = document.createElement('tr');
  var fileloc_label = document.createElement('td');
  fileloc_label.innerHTML = 'File Location';
  fileloc_row.appendChild(fileloc_label);
  var fileloc_select = document.createElement('select');
  fileloc_select.setAttribute('data-pname', 'loc');
  fileloc_select.addEventListener('change', this._file_on_attribute_update.bind(this));
  for ( var fileloc in _VIA_FILE_LOC ) {
    var oi = document.createElement('option');
    oi.setAttribute('value', _VIA_FILE_LOC[fileloc]);
    oi.innerHTML = fileloc;
    if ( this.d.store.file[this.fid].loc === _VIA_FILE_LOC[fileloc] ) {
      oi.setAttribute('selected', '');
    }
    fileloc_select.appendChild(oi);
  }
  var fileloc_cell = document.createElement('td');
  fileloc_cell.appendChild(fileloc_select);
  if ( this.d.store.file[this.fid].loc !== _VIA_FILE_LOC.LOCAL ) {
    var fileloc = this.d.store.file[this.fid].loc;
    var locprefix_input = document.createElement('input');
    locprefix_input.setAttribute('type', 'text');
    locprefix_input.setAttribute('value', this.d.store.config.file.loc_prefix[fileloc]);
    locprefix_input.setAttribute('data-pname', 'loc_prefix');
    locprefix_input.setAttribute('title', 'Location prefix (or path) that will be automatically added to file locations. For example, if you add "http://www.mysite.com/data/images/" as the location prefix, all your images will be sourced from this site.');
    locprefix_input.addEventListener('change', this._file_on_attribute_update.bind(this));
    fileloc_cell.appendChild(locprefix_input);
  }
  fileloc_row.appendChild(fileloc_cell);
  page.appendChild(fileloc_row);

  var filesrc_row = document.createElement('tr');
  var filesrc_label = document.createElement('td');
  filesrc_label.innerHTML = 'File Source';
  filesrc_row.appendChild(filesrc_label);
  var filesrc_input;
  if ( this.d.store.file[this.fid].loc === _VIA_FILE_LOC.LOCAL ) {
    filesrc_input = document.createElement('input');
    filesrc_input.setAttribute('type', 'file');
    if ( this.d.file_ref[this.fid] ) {
      filesrc_input.setAttribute('files', [ this.d.file_ref[this.fid] ]);
    }
  } else {
    if ( this.d.store.file[this.fid].loc === _VIA_FILE_LOC.INLINE ) {
      filesrc_input = document.createElement('textarea');
      filesrc_input.setAttribute('rows', 5);
      filesrc_input.setAttribute('cols', 100);
      filesrc_input.innerHTML = this.d.store.file[this.fid].src;
    } else {
      filesrc_input = document.createElement('input');
      filesrc_input.setAttribute('type', 'text');
      filesrc_input.setAttribute('value', this.d.store.file[this.fid].src)
    }
  }
  filesrc_input.setAttribute('data-pname', 'src');
  filesrc_input.addEventListener('change', this._file_on_attribute_update.bind(this));
  var filesrc_cell = document.createElement('td');
  filesrc_cell.appendChild(filesrc_input);
  filesrc_row.appendChild(filesrc_cell);
  page.appendChild(filesrc_row);

  // control buttons
  var bpanel = document.createElement('p');
  var reload = document.createElement('button');
  reload.innerHTML = 'Reload File';
  reload.addEventListener('click', function() {
    this.va.view_show(this.vid);
  }.bind(this));
  bpanel.appendChild(reload);
  page.appendChild(bpanel);

  this.c.appendChild(page);
}

_via_file_annotator.prototype._file_on_attribute_update = function(e) {
  var pname = e.target.dataset.pname;
  var pvalue = '';
  switch(pname) {
  case 'loc_prefix':
  case 'fname':
    pvalue = e.target.value;
    break;
  case 'type':
  case 'loc':
    pvalue = parseInt(e.target.options[e.target.selectedIndex].value);
    break;
  case 'src':
    if ( this.d.store.file[this.fid].loc === _VIA_FILE_LOC.LOCAL ) {
      if ( e.target.files.length ) {
        pvalue = e.target.files[0];
      }
    } else {
      if ( this.d.store.file[this.fid].loc === _VIA_FILE_LOC.INLINE ) {
        pvalue = e.target.innerHTML;
      } else {
        pvalue = e.target.value;
      }
    }
    break;
  }

  this.d.file_update(this.fid, pname, pvalue).then( function(ok) {
    this.va.view_show(this.vid);
  }.bind(this), function(err) {
    _via_util_msg_show('Failed to update properties of file: ' + err );
  }.bind(this));
}

_via_file_annotator.prototype._file_load = function() {
  return new Promise( function(ok_callback, err_callback) {
    this.file_html_element = this._file_create_html_element();
    this.file_html_element.setAttribute('title', this.d.store.file[this.fid].fname);
    var file_src = this.d.file_get_src(this.d.store.file[this.fid].fid);
    if ( file_src === '' ) {
      this.d.file_free_resources(this.fid);
      this._file_load_show_error_page();
      err_callback();
      return;
    } else {
      this.file_html_element.setAttribute('src', file_src);
    }

    this.file_html_element.addEventListener('load', function() {
      this._file_html_element_ready();
      ok_callback();
    }.bind(this));
    this.file_html_element.addEventListener('loadeddata', function() {
      this._file_html_element_ready();
      ok_callback();
    }.bind(this));
    this.file_html_element.addEventListener('abort', function(e) {
      _via_util_msg_show('File load aborted [' + this.d.store.file[this.fid].fname + ']' );
      this._file_load_show_error_page();
      err_callback();
    }.bind(this));
    this.file_html_element.addEventListener('stalled', function(e) {
      _via_util_msg_show('File load stalled [' + this.d.store.file[this.fid].fname + ']' );
      this._file_load_show_error_page();
      err_callback();
    }.bind(this));
    this.file_html_element.addEventListener('error', function(e) {
      _via_util_msg_show('Error loading file [' + this.d.store.file[this.fid].fname + ']' );
      this._file_load_show_error_page();
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._file_create_html_element = function() {
  var media;
  switch( this.d.store.file[this.fid].type ) {
  case _VIA_FILE_TYPE.VIDEO:
    media = document.createElement('video');
    media.setAttribute('controls', 'true');
    media.setAttribute('playsinline', 'true');
    media.setAttribute('loop', 'false');
    //media.setAttribute('crossorigin', 'anonymous');
    // @todo : add subtitle track for video
    media.setAttribute('preload', 'auto');
    media.addEventListener('pause', function(e) {
      this._creg_show_current_frame_regions();
      this._rinput_enable();
    }.bind(this));
    media.addEventListener('play', function(e) {
      this._creg_clear();
      this._rinput_disable();
    }.bind(this));
    media.addEventListener('seeked', function(e) {
      this._creg_show_current_frame_regions();
      this._rinput_enable();
      this._smetadata_hide();
    }.bind(this));

    //media.addEventListener('suspend', this._file_html_element_error.bind(this));
    break;

  case _VIA_FILE_TYPE.IMAGE:
    media = document.createElement('img');
    break;

  case _VIA_FILE_TYPE.AUDIO:
    media = document.createElement('audio');
    media.setAttribute('controls', '');
    // @todo : add subtitle track for video
    media.setAttribute('preload', 'auto');
    break;

  default:
    console.warn('unknown file type = ' + this.d.store.file[this.fid].type);
  }
  return media;
}

_via_file_annotator.prototype._file_html_element_compute_scale = function() {
  var maxh = this.c.clientHeight;
  var maxw = this.c.clientWidth;

  // original size of the content
  var cw0, ch0;
  switch( this.d.store.file[this.fid].type ) {
  case _VIA_FILE_TYPE.VIDEO:
    cw0 = this.file_html_element.videoWidth;
    ch0 = this.file_html_element.videoHeight;
    break;
  case _VIA_FILE_TYPE.IMAGE:
    cw0 = this.file_html_element.naturalWidth;
    ch0 = this.file_html_element.naturalHeight;
    break;

  case _VIA_FILE_TYPE.AUDIO:
    this.left_pad = 0;
    this.file_html_element_size_css = '';
    return;
    break;
  }

  var ar = cw0/ch0;
  var ch, cw;
  var ch = ch0;
  var cw = Math.floor(ar * ch);
  switch(this.va.zoom_mode) {
  case _VIA_ZOOM_MODE.FITHEIGHT:
    ch = maxh;
    cw = Math.floor(ar * ch);
    if ( cw > maxw ) {
      cw = maxw;
      ch = Math.floor(cw/ar);
    }

    break;
  case _VIA_ZOOM_MODE.FITWIDTH:
    cw = maxw;
    ch = Math.floor(cw/ar);
    break;
  default:
    var zoom_scale_value = _VIA_ZOOM_SCALE_VALUE_LIST[this.va.zoom_scale_value_index];
    ch = Math.floor(zoom_scale_value * ch0);
    cw = Math.floor(ar * ch);
  }
  this.cwidth = cw;
  this.cheight = ch;
  this.cscale = ch0/ch; // x  = cscale * cx
  this.fscale = 1 / this.cscale; // cx = fscale * x
  this.original_width = cw0;
  this.original_height = ch0;
  this.file_html_element_size_css = 'width:' + cw + 'px;height:' + ch + 'px;';

  switch( this.d.store.config.ui.file_content_align ) {
  case 'center':
    this.left_pad = Math.floor( (maxw - this.cwidth) / 2 );
    this.file_html_element_size_css += 'left:' + this.left_pad + 'px;';
    break;
  case 'right':
    this.left_pad = maxw - this.cwidth;
    this.file_html_element_size_css += 'left:' + this.left_pad + 'px;';
    break;
  default:
    this.left_pad = 0;
    this.file_html_element_size_css += 'left:0px;';
  }
}

//
// event listeners
//
_via_file_annotator.prototype._file_html_element_ready = function() {
  //_via_util_msg_show('Loaded file [' + this.d.store.file[this.fid].fname + ']' );
  this._file_html_element_compute_scale();
  this.file_html_element.setAttribute('style', this.file_html_element_size_css);
  this.file_html_element.setAttribute('id', 'file_content');
  this.c.appendChild(this.file_html_element);

  // add canvas for region shape
  this.rshape_canvas = document.createElement('canvas');
  this.rshape_canvas.setAttribute('style', this.file_html_element_size_css);
  this.rshape_canvas.setAttribute('id', 'region_shape');
  this.rshape_canvas.style.pointerEvents = 'none';
  this.rshape_canvas.width = this.cwidth;
  this.rshape_canvas.height = this.cheight;
  this.rshapectx = this.rshape_canvas.getContext('2d', { alpha:true });
  this.c.appendChild(this.rshape_canvas);

  this.tempr_canvas = document.createElement('canvas');
  this.tempr_canvas.setAttribute('style', this.file_html_element_size_css);
  this.tempr_canvas.setAttribute('id', 'region_input');
  this.tempr_canvas.style.pointerEvents = 'none';
  this.tempr_canvas.width = this.cwidth;
  this.tempr_canvas.height = this.cheight;
  this.temprctx = this.tempr_canvas.getContext('2d', { alpha:true });
  this.c.appendChild(this.tempr_canvas);

  // zoom container
  this.magnifier_container = document.createElement('div');
  this.magnifier_container.setAttribute('class', 'magnifier_container');
  this.magnifier_container.classList.add('hide');
  this.c.appendChild(this.magnifier_container);

  // keyboard and mouse input handlers
  this.input = document.createElement('div');
  this.input.setAttribute('style', this.file_html_element_size_css);
  this.input.setAttribute('id', 'input');
  this.input.style.pointerEvents = 'none';
  this._rinput_attach_input_handlers(this.input);
  this.c.appendChild(this.input);

  // spatial metadata container (i.e. metadata of image or video frame regions)
  this.smetadata_container = document.createElement('div');
  this.smetadata_container.setAttribute('class', 'metadata_container');
  this.smetadata_container.classList.add('hide');
  this.smetadata_container.setAttribute('id', 'smetadata_container');
  this.smetadata_container.innerHTML = '';
  this.c.appendChild(this.smetadata_container);

  // file metadata container (e.g. caption)
  this.fmetadata_container = document.createElement('div');
  this.fmetadata_container.setAttribute('class', 'metadata_container');
  this.fmetadata_container.classList.add('hide');
  this.fmetadata_container.setAttribute('id', 'fmetadata_container');
  this.fmetadata_container.innerHTML = '';
  this.c.appendChild(this.fmetadata_container);
  this._fmetadata_show();

  // draw all existing regions
  this._creg_draw_file_label();
  this._creg_update();
  this._creg_draw_all();

  this._state_set(_VIA_RINPUT_STATE.IDLE);
}

//
// input event listeners
//
_via_file_annotator.prototype._rinput_attach_input_handlers = function(container) {
  container.addEventListener('mousedown', this._rinput_mousedown_handler.bind(this));
  container.addEventListener('mouseup', this._rinput_mouseup_handler.bind(this));
  container.addEventListener('mousemove', this._rinput_mousemove_handler.bind(this));
  container.addEventListener('mouseout', this._rinput_mouseout_handler.bind(this));
  container.addEventListener('mouseover', this._rinput_mouseover_handler.bind(this));

  container.addEventListener('wheel', this._rinput_wheel_handler.bind(this));

  container.addEventListener('keydown', this._rinput_keydown_handler.bind(this));
}

_via_file_annotator.prototype._rinput_remove_input_handlers = function() {
  // @todo
}

_via_file_annotator.prototype._rinput_keydown_handler = function(e) {
  if ( e.key === 'b' || e.key === 'l' ) {
    if(e.key === 'b') {
      this.show_region_shape = !this.show_region_shape;
    } else {
      this.show_region_label = !this.show_region_label;
    }
    this._creg_clear();
    this._creg_draw_all();
    return;
  }
  
  if ( e.key === 'n' || e.key === 'p' ) {
    e.preventDefault();
    if(e.key === 'n') {
      this.va.emit_event('view_next', {});
    } else {
      this.va.emit_event('view_prev', {});
    }
    return;
  }

  if ( e.key === 'Backspace' || e.key === 'Delete' ) {
    if ( this.selected_mid_list.length ) {
      e.preventDefault();
      this._creg_del_sel_regions();
      _via_util_msg_show('Spatial region deleted.');
    }
    return;
  }

  if ( e.key === 'a' ) {
    if ( e.ctrlKey ) {
      e.preventDefault();
      this._creg_select_all();
      this._creg_draw_all();
      _via_util_msg_show('Selected all regions.');
    }
    return;
  }

  if ( e.key === 'ArrowLeft' || e.key === 'ArrowRight' ||
       e.key === 'ArrowUp' || e.key === 'ArrowDown'
     ) {
    if ( this.selected_mid_list.length ) {
      e.preventDefault();
      // move selected region
      var cdx = 0;
      var cdy = 0;
      switch(e.key) {
      case 'ArrowLeft':
        cdx = -1;
        break;
      case 'ArrowRight':
        cdx = +1;
        break;
      case 'ArrowUp':
        cdy = -1;
        break;
      case 'ArrowDown':
        cdy = +1;
        break;
      }
      if ( e.shiftKey ) {
        cdx = cdx * _VIA_SPATIAL_REGION_MOVE_DELTA;
        cdy = cdy * _VIA_SPATIAL_REGION_MOVE_DELTA;
      }
      var mid_list = this.selected_mid_list.slice(0);
      this._metadata_move_region(mid_list, cdx, cdy);
    }
    return;
  }

  if ( e.key === '-' ) {
    if(this._is_magnifier_enabled) {
      if(this.magnifier_scale_index > 0) {
        this.magnifier_scale_index = this.magnifier_scale_index - 1;
        this.magnifier_scale = this.magnifier_scale_list[this.magnifier_scale_index];
        _via_util_msg_show('Zoom scale reduced to ' + this.magnifier_scale);
        this.magnifier_container.classList.add('hide');
        this._magnifier_activate();
        this.magnifier_container.classList.remove('hide');
      } else {
        _via_util_msg_show('Reached minimum limit of zoom');
      }
    }
  }

  if ( e.shiftKey && e.key === '+') {
    if(this._is_magnifier_enabled) {
      if(this.magnifier_scale_index < this.magnifier_scale_list.length) {
        this.magnifier_scale_index = this.magnifier_scale_index + 1;
        this.magnifier_scale = this.magnifier_scale_list[this.magnifier_scale_index];
        _via_util_msg_show('Zoom scale increased to ' + this.magnifier_scale);
        this.magnifier_container.classList.add('hide');
        this._magnifier_activate();
        this.magnifier_container.classList.remove('hide');
      } else {
        _via_util_msg_show('Reached maximum limit of zoom');
      }
    }
  }

  if ( e.key === 'Escape' ) {
    e.preventDefault();
    if ( this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING &&
         this.user_input_pts.length > 2
       ) {
      this._rinput_cancel_last_nclick();
      this._tmpreg_clear();
      var pts = this.user_input_pts.slice(0);
      pts.push(this.last_cx, this.last_cy);
      this._tmpreg_draw_region(this.va.region_draw_shape, pts);
      _via_util_msg_show('Discarded last drawn vertex.');
    } else {
      this._creg_select_none();
      this._smetadata_hide();
      this._tmpreg_clear();
      this.user_input_pts = [];
      this._state_set( _VIA_RINPUT_STATE.IDLE );
      _via_util_msg_show('Reset done.');
    }
    this._creg_draw_all();
    return;
  }

  if ( e.key === 'Enter' ) {
    e.preventDefault();
    // For extreme box, we do not want to allow finishing the drawing unless
    // all 4 extreme points have been marked, at which point we automatically
    // finish the drawing, anyway.
    if ( this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING &&
         this.user_input_pts.length > 4 &&
         this.va.region_draw_shape != _VIA_RSHAPE.EXTREME_BOX) {
      this._rinput_region_draw_nclick_done();
      this.user_input_pts = [];
      this._tmpreg_clear();
      this._state_set( _VIA_RINPUT_STATE.IDLE );
      _via_util_msg_show( 'Finished drawing a region shape with multiple vertices.');
    } else {
      if (this.va.region_draw_shape == _VIA_RSHAPE.EXTREME_BOX) {
        _via_util_msg_show('You must define all 4 vertices. Press <span class="key">Esc</span> to cancel last drawn vertex.');
      } else {
        _via_util_msg_show('You must define at least 2 vertices. Press <span class="key">Esc</span> to cancel last drawn vertex.');
      }
    }
  }
}

_via_file_annotator.prototype._rinput_cancel_last_nclick = function() {
  var n = this.user_input_pts.length;
  this.user_input_pts.splice(n-2, 2); // delete last two points
}

_via_file_annotator.prototype._rinput_mousedown_handler = function(e) {
  e.stopPropagation();
  var cx = e.offsetX;
  var cy = e.offsetY;
  //console.log('[vid=' + this.vid + ', state=' + this._state_id2str(this.state_id) + '] : mousedown at (cx,cy) = (' + cx + ',' + cy + ')');

  if ( this.state_id === _VIA_RINPUT_STATE.IDLE ) {
    if ( e.shiftkey ) {
      this.user_input_pts.push(cx, cy);
      this._state_set( _VIA_RINPUT_STATE.SELECT_ALL_INSIDE_AN_AREA_ONGOING );
    } else {
      // is this mousedown inside a region?
      this.last_clicked_mid_list = this._is_point_inside_existing_regions(cx, cy);
      if ( this.last_clicked_mid_list.length ) {
        // two possibilities:
        // 1. Draw region inside an existing region
        // 2. Select the region
        this._state_set( _VIA_RINPUT_STATE.REGION_SELECT_OR_DRAW_POSSIBLE );
      } else {
        // draw region
        this.user_input_pts.push(cx, cy);
        this._state_set( _VIA_RINPUT_STATE.REGION_DRAW_ONGOING );
      }
    }
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING ) {
    var nclick_done = false;
    switch( this.va.region_draw_shape ) {
    case _VIA_RSHAPE.EXTREME_RECTANGLE:
      this.user_input_pts.push(cx, cy);
      if ( this.user_input_pts.length === 8 ) {
        nclick_done = true;
      }
      break;
    case _VIA_RSHAPE.EXTREME_CIRCLE:
      this.user_input_pts.push(cx, cy);
      if ( this.user_input_pts.length === 6 ) {
        nclick_done = true;
      }
      break;
    case _VIA_RSHAPE.POLYGON:
    case _VIA_RSHAPE.POLYLINE:
      if ( this._rinput_is_near_first_user_input_point(cx, cy) ) {
        nclick_done = true;
      } else {
        this.user_input_pts.push(cx, cy);
      }
      break;
    }
    if ( nclick_done ) {
      this._rinput_region_draw_nclick_done();
      this.user_input_pts = [];
      this._tmpreg_clear();
      this._state_set( _VIA_RINPUT_STATE.IDLE );
      //_via_util_msg_show( 'Finished drawing a region shape with multiple vertices.');
    }
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_SELECTED ) {
    var sel_region_cp = this._creg_is_on_sel_region_cp(cx, cy,
                                                       this.conf.CONTROL_POINT_CLICK_TOL);
    if ( sel_region_cp[0] !== -1 ) {
      // mousedown was on control point of one of the selected regions
      this.resize_selected_mid_index = sel_region_cp[0];
      this.resize_control_point_index = sel_region_cp[1];
      this.resize_type = sel_region_cp[2];
      if(this.resize_type === 'edge') {
        this.resize_edge_xy = [cx, cy];
      }
      this._state_set( _VIA_RINPUT_STATE.REGION_RESIZE_ONGOING );
    } else {
      // mousedown was not on a control point, two possibilities:
      // - inside an already selected region
      // - outside a selected region
      //   * inside another unselected region
      //   * outside any region
      var mid_list = this._is_point_inside_existing_regions(cx, cy);
      if ( e.shiftKey ) { // used to select multiple regions or unselect one of existing regions
        if ( mid_list.length === 0 ) {
          // outside a region, hence it could be to select regions inside a user drawn area
          this.user_input_pts.push(cx, cy);
          this._state_set( _VIA_RINPUT_STATE.SELECT_ALL_INSIDE_AN_AREA_ONGOING );
        } else {
          // inside a region, hence toggle selection
          this.last_clicked_mid_list = mid_list;
          this._state_set( _VIA_RINPUT_STATE.REGION_SELECT_TOGGLE_ONGOING );
        }
      } else {
        if ( mid_list.length === 0 ) {
          this._state_set( _VIA_RINPUT_STATE.REGION_UNSELECT_ONGOING );
        } else {
          var sel_mindex = this._is_point_inside_sel_regions(cx, cy);
          if ( sel_mindex === -1 ) {
            this.last_clicked_mid_list = mid_list;
            this._state_set( _VIA_RINPUT_STATE.REGION_SELECT_OR_DRAW_POSSIBLE );
          } else {
            this.user_input_pts.push(cx, cy);
            this._state_set( _VIA_RINPUT_STATE.REGION_MOVE_ONGOING );
          }
        }
      }
    }
    return;
  }
}

_via_file_annotator.prototype._rinput_mouseup_handler = function(e) {
  e.stopPropagation();
  var cx = e.offsetX;
  var cy = e.offsetY;
  //console.log('[vid=' + this.vid + ', state=' + this._state_id2str(this.state_id) + '] : mouseup at (cx,cy) = (' + cx + ',' + cy + ')');

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_ONGOING ) {
    switch ( this.va.region_draw_shape ) {
    case _VIA_RSHAPE.EXTREME_RECTANGLE:
      this._state_set( _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING );
      _via_util_msg_show( 'First boundary point added. Now click at three remaining points to mark the boundary of a rectangular object.', true);
      break;
    case _VIA_RSHAPE.EXTREME_CIRCLE:
      this._state_set( _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING );
      _via_util_msg_show( 'First point on added. Now click at two remaining points on the circumference to define a circular region.', true);
      break;
    case _VIA_RSHAPE.POLYGON:
    case _VIA_RSHAPE.POLYLINE:
      // region shape requiring more than two points (polygon, polyline)
      this._state_set( _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING );
      _via_util_msg_show( 'To finish, click at the first vertex or press <span class="key">Enter</span> key. To discard the last drawn vertex, press <span class="key">Esc</span> key.', true);
      break;

    default:
      // region shape requiring just two points (rectangle, circle, ellipse, etc.)
      this.user_input_pts.push(cx, cy);
      if ( this._is_user_input_pts_equal() ) {
        if ( this.va.region_draw_shape !== _VIA_RSHAPE.POINT ) {
          _via_util_msg_show('Discarded degenerate region. Press <span class="key">Space</span> key to play or pause video.');
        } else {
          var canvas_input_pts = this.user_input_pts.slice(0);
          this._metadata_add(this.va.region_draw_shape, canvas_input_pts);
        }
      } else {
        var canvas_input_pts = this.user_input_pts.slice(0);
        this._metadata_add(this.va.region_draw_shape, canvas_input_pts);
      }
      this.user_input_pts = [];
      this._tmpreg_clear();
      this._state_set( _VIA_RINPUT_STATE.IDLE );
    }
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_SELECT_OR_DRAW_POSSIBLE ) {
    if ( ! e.shiftKey ) {
      this._creg_select_none();
    }
    this._tmpreg_clear();
    if(this.last_clicked_mid_list.length) {
      this._creg_select( this.last_clicked_mid_list[0] );
    }
    this._smetadata_show();
    this._creg_draw_all();
    this._state_set( _VIA_RINPUT_STATE.REGION_SELECTED );
    _via_util_msg_show('Region selected. Press <span class="key">Backspace</span> key to delete and arrow keys to move selected region. Use mouse wheel to update region label.', true);
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_MOVE_ONGOING ) {
    this.user_input_pts.push(cx, cy);
    // region shape requiring just two points (rectangle, circle, ellipse, etc.)
    if ( this._is_user_input_pts_equal() ) {
      // implies user performed a click operation
      // check if click is on another region
      var clicked_mid_list = this._is_point_inside_existing_regions(cx, cy);
      if(clicked_mid_list.length) {
        if(clicked_mid_list[0] === this.last_clicked_mid_list[0]) {
          this._creg_select_none();
          this.user_input_pts = [];
          this._state_set( _VIA_RINPUT_STATE.IDLE );
        } else {
          // select the new region
          if ( e.shiftKey ) {
            this._creg_select( clicked_mid_list[0] );
          } else {
            this._creg_select_one( clicked_mid_list[0] );
          }
          this._state_set( _VIA_RINPUT_STATE.REGION_SELECTED );
        }
        this._smetadata_show();
        this._creg_draw_all();
      }
      this.user_input_pts = [];
    } else {
      var canvas_input_pts = this.user_input_pts.slice(0);
      var cdx = canvas_input_pts[2] - canvas_input_pts[0];
      var cdy = canvas_input_pts[3] - canvas_input_pts[1];
      var mid_list = this.selected_mid_list.slice(0);
      this._metadata_move_region(mid_list, cdx, cdy);
      if(this._is_magnifier_enabled) {
        this.magnifier_rshape_ctx.clearRect(0, 0, this.magnifier_canvas_width, this.magnifier_canvas_height); // required to clear old region
      }
      this._tmpreg_clear();
      this.user_input_pts = [];
      this._state_set( _VIA_RINPUT_STATE.REGION_SELECTED );
    }
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_RESIZE_ONGOING ) {
    var resize_mid = this.selected_mid_list[this.resize_selected_mid_index];
    var shape_id = this.creg[resize_mid][0];
    if((shape_id === _VIA_RSHAPE.POLYLINE || shape_id === _VIA_RSHAPE.POLYGON) &&
       (e.ctrlKey || e.metaKey)) {
      // if on vertex, delete it
      // if on edge, add a new vertex
      var npts = this.d.store.metadata[resize_mid].xy.length - 1; // -1 because first element is shape_id
      if((shape_id === _VIA_RSHAPE.POLYLINE && npts === 4) ||
         (shape_id === _VIA_RSHAPE.POLYGON && npts === 6)) {
        _via_util_msg_show('Cannot delete vertex as it would result in a degenerate region');
      } else {
        if(this.resize_type === 'cp') {
          // delete vertex
          this._metadata_polygon_del_vertex(this.resize_selected_mid_index,
                                            this.resize_control_point_index);
        } else if(this.resize_type === 'edge') {
          // add a new vertex
          this._metadata_polygon_add_vertex(this.resize_selected_mid_index,
                                            this.resize_control_point_index,
                                            this.resize_edge_xy[0],
                                            this.resize_edge_xy[1]);
        }
      }
    } else {
      this._metadata_resize_region(this.resize_selected_mid_index,
                                   this.resize_control_point_index,
                                   cx, cy);
    }

    if(this._is_magnifier_enabled) {
      this.magnifier_rshape_ctx.clearRect(0, 0, this.magnifier_canvas_width, this.magnifier_canvas_height); // required to clear old region
    }
    this._tmpreg_clear();
    this.user_input_pts = [];
    this.resized_selected_mid_index = -1;
    this.resize_control_point_index = -1;
    this.resize_type = '';
    this._state_set( _VIA_RINPUT_STATE.REGION_SELECTED );
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_UNSELECT_ONGOING ) {
    this._creg_select_none();
    this._smetadata_hide();
    this._creg_draw_all();
    this.user_input_pts = [];
    this._state_set( _VIA_RINPUT_STATE.IDLE );
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.SELECT_ALL_INSIDE_AN_AREA_ONGOING ) {
    // @todo
    this._creg_select_none();
    this._smetadata_hide();
    this._creg_draw_all();
    this.user_input_pts = [];
    this._state_set( _VIA_RINPUT_STATE.IDLE );
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_SELECT_TOGGLE_ONGOING ) {
    if ( ! e.shiftKey ) {
      this._creg_select_none();
    }
    this._creg_select_toggle( this.last_clicked_mid_list );
    this._smetadata_show();
    this._creg_draw_all();
    this._state_set( _VIA_RINPUT_STATE.REGION_SELECTED );
    return;
  }
}

_via_file_annotator.prototype._rinput_mousemove_handler = function(e) {
  e.stopPropagation();
  var cx = e.offsetX;
  var cy = e.offsetY;
  this.last_cx = cx;
  this.last_cy = cy;

  if(this._is_magnifier_enabled) {
    this._magnifier_update_position();
  }

  var pts = this.user_input_pts.slice(0);
  pts.push(cx, cy);

  this._tmpreg_clear();
  if ( this.va.region_draw_shape === _VIA_RSHAPE.EXTREME_RECTANGLE ) {
    if ( this.state_id !== _VIA_RINPUT_STATE.REGION_SELECTED ) {
      this._tmpreg_draw_crosshair(this.last_cx, this.last_cy);
    }
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_ONGOING ||
       this.state_id === _VIA_RINPUT_STATE.REGION_DRAW_NCLICK_ONGOING
     ) {
    this._tmpreg_draw_region(this.va.region_draw_shape, pts);
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_SELECTED ) {
    // sel_region_cp = [metadata_index, control_point_index, type = {'edge', 'cp'}]
    var sel_region_cp = this._creg_is_on_sel_region_cp(cx, cy,
                                                       this.conf.CONTROL_POINT_CLICK_TOL);

    if ( sel_region_cp[0] !== -1 && sel_region_cp[1] !== -1 ) {
      var mindex = sel_region_cp[0];
      var mid = this.selected_mid_list[mindex];
      var cp_index = sel_region_cp[1];
      var shape_id = this.creg[mid][0];

      switch(shape_id) {
      case _VIA_RSHAPE.RECTANGLE:
      case _VIA_RSHAPE.CIRCLE:
      case _VIA_RSHAPE.ELLIPSE:
        switch(cp_index) {
        case 1: // top center
        case 3: // bottom center
          this.input.style.cursor = 'row-resize';
          break;
        case 2: // right center
        case 4: // left center
          this.input.style.cursor = 'col-resize';
          break;
        case 5: // corner top-right
        case 7: // corner bottom-left
          this.input.style.cursor = 'nesw-resize';
          break;
        case 6: // corner bottom-right
        case 8: // corner top-left
          this.input.style.cursor = 'nwse-resize';
          break;
        }
        break;
      case _VIA_RSHAPE.EXTREME_RECTANGLE:
      case _VIA_RSHAPE.EXTREME_CIRCLE:
      case _VIA_RSHAPE.POINT:
      case _VIA_RSHAPE.LINE:
      case _VIA_RSHAPE.POLYGON:
      case _VIA_RSHAPE.POLYLINE:
        this.input.style.cursor = 'crosshair';
        // fall through and show message if it is polygon or polyline
      case _VIA_RSHAPE.POLYGON:
      case _VIA_RSHAPE.POLYLINE:
        _via_util_msg_show('To move vertex, simply drag the vertex. To add vertex, press [Ctrl] key and click on the edge. To delete vertex, press [Ctrl] (or [Command]) key and click on vertex.');
        break;
      }
    } else {
      if( this._is_point_inside_sel_regions(cx, cy) === -1 ) {
        this.input.style.cursor = 'default';
      } else {
        this.input.style.cursor = 'move';
      }
    }
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_MOVE_ONGOING ) {
    this._tmpreg_clear();
    var dx = cx - this.user_input_pts[0];
    var dy = cy - this.user_input_pts[1];
    this._tmpreg_move_sel_regions(dx, dy);
    return;
  }

  if ( this.state_id === _VIA_RINPUT_STATE.REGION_RESIZE_ONGOING ) {
    this._tmpreg_clear();
    this._tmpreg_move_sel_region_cp(this.resize_selected_mid_index,
                                    this.resize_control_point_index,
                                    cx, cy);
    return;
  }
}

_via_file_annotator.prototype._rinput_mouseout_handler = function(e) {
  e.stopPropagation();
  if(this._is_magnifier_enabled) {
    this.magnifier_container.classList.add('hide');
  }
}

_via_file_annotator.prototype._rinput_mouseover_handler = function(e) {
  e.stopPropagation();
  if(this._is_magnifier_enabled) {
    this.magnifier_container.classList.remove('hide');
  }
}

_via_file_annotator.prototype._rinput_pts_canvas_to_file = function(canvas_input_pts) {
  var file_input_pts = canvas_input_pts.slice(0);
  var n = canvas_input_pts.length;
  var x, y;
  for ( var i = 0; i < n; ++i ) {
    file_input_pts[i] = parseFloat((canvas_input_pts[i] * this.cscale).toFixed(3));
  }
  return file_input_pts;
}

_via_file_annotator.prototype._rinput_is_near_last_user_input_point = function(cx, cy) {
  var n = this.user_input_pts.length;
  if ( n >= 2 ) {
    var dx = Math.abs(cx - this.user_input_pts[n-2]);
    var dy = Math.abs(cy - this.user_input_pts[n-1]);
    if ( dx <= this.conf.CONTROL_POINT_CLICK_TOL &&
         dy <= this.conf.CONTROL_POINT_CLICK_TOL ) {
      return true;
    }
  }
  return false;
}

_via_file_annotator.prototype._rinput_is_near_first_user_input_point = function(cx, cy) {
  var n = this.user_input_pts.length;
  if ( n >= 2 ) {
    var dx = Math.abs(cx - this.user_input_pts[0]);
    var dy = Math.abs(cy - this.user_input_pts[1]);
    if ( dx <= this.conf.CONTROL_POINT_CLICK_TOL &&
         dy <= this.conf.CONTROL_POINT_CLICK_TOL ) {
      return true;
    }
  }
  return false;
}

_via_file_annotator.prototype._rinput_region_draw_nclick_done = function() {
  var canvas_input_pts = this.user_input_pts.slice(0);
  this._metadata_add(this.va.region_draw_shape, canvas_input_pts);
}

_via_file_annotator.prototype._rinput_wheel_handler = function(e) {
  if ( this.selected_mid_list.length ) {
    e.preventDefault();
    var aid_list = Object.keys(this.d.store.attribute);
    if ( this.d.store.config.ui['spatial_region_label_attribute_id'] === '' ) {
      this.d.store.config.ui['spatial_region_label_attribute_id'] = aid_list[0];
    } else {
      var aid_index = aid_list.indexOf(this.d.store.config.ui['spatial_region_label_attribute_id']);
      if ( aid_index !== -1 ) {
        if (e.deltaY < 0) {
          var next_aid_index = aid_index + 1;
          if ( next_aid_index >= aid_list.length ) {
            this.d.store.config.ui['spatial_region_label_attribute_id'] = '';
          } else {
            this.d.store.config.ui['spatial_region_label_attribute_id'] = aid_list[next_aid_index];
          }
        } else {
          var prev_aid_index = aid_index - 1;
          if ( prev_aid_index < 0 ) {
            this.d.store.config.ui['spatial_region_label_attribute_id'] = '';
          } else {
            this.d.store.config.ui['spatial_region_label_attribute_id'] = aid_list[prev_aid_index];
          }
        }
      } else {
        this.d.store.config.ui['spatial_region_label_attribute_id'] = '';
      }
    }
    this._creg_update();
    this._creg_draw_all();
  }
}


//
// user input state
//
_via_file_annotator.prototype._state_set = function(state_id) {
  this.state_id = state_id;
  //console.log('[vid=' + this.vid + '] State = ' + this._state_id2str(this.state_id));
}

_via_file_annotator.prototype._state_id2str = function(state_id) {
  for ( var state in _VIA_RINPUT_STATE ) {
    if ( _VIA_RINPUT_STATE[state] === state_id ) {
      return state;
    }
  }
  return '';
}

_via_file_annotator.prototype._state_str2id = function(state) {
  if ( _VIA_RINPUT_STATE.hasOwnProperty(state) ) {
    return _VIA_RINPUT_STATE[state];
  } else {
    return -1;
  }
}

//
// region probes
//
_via_file_annotator.prototype._is_user_input_pts_equal = function() {
  var n = this.user_input_pts.length;
  if ( n >= 4 ) {
    if ( this.user_input_pts[0] === this.user_input_pts[2] &&
         this.user_input_pts[1] === this.user_input_pts[3]
       ) {
      return true;
    }
  }
  return false;
}

_via_file_annotator.prototype._is_point_inside_existing_regions = function(cx, cy) {
  var mid_list = [];
  var mid_edge_dist = [];
  var dist_minmax;
  for ( var mid in this.creg ) {
    if ( this._creg_is_inside(this.creg[mid],
                              cx,
                              cy,
                              this.conf.CONTROL_POINT_CLICK_TOL) ) {

      mid_list.push(mid);
    }
  }

  if(mid_list.length) {
    // if multiple regions, sort mid based on distance on (cx,cy) to its nearest edge
    var dist_minmax;
    for( var mindex in mid_list ) {
      dist_minmax = this._creg_edge_minmax_dist_to_point(this.creg[ mid_list[mindex] ], cx, cy);
      mid_edge_dist.push(dist_minmax[0]);
    }

    mid_list.sort( function(mid1, mid2) {
      if( mid_edge_dist[ mid_list.indexOf(mid1) ] < mid_edge_dist[ mid_list.indexOf(mid2) ] ) {
        return -1;
      } else {
        return 1;
      }
    });
  }
  return mid_list;
}

_via_file_annotator.prototype._is_point_inside_sel_regions = function(cx, cy) {
  var mid, mindex;
  for ( mindex in this.selected_mid_list ) {
    mid = this.selected_mid_list[mindex];
    if ( this._creg_is_inside(this.creg[mid],
                              cx,
                              cy,
                              this.conf.CONTROL_POINT_CLICK_TOL) ) {
      return mindex;
    }
  }
  return -1;
}

//
// metadata
//
_via_file_annotator.prototype._metadata_polygon_del_vertex = function(mindex, cpindex) {
  return new Promise( function(ok_callback, err_callback) {
    var mid = this.selected_mid_list[mindex];
    var xy_with_del_cp = this.d.store.metadata[mid].xy.slice();
    // note: cpindex is indexed from 1
    var del_cpindex = cpindex - 1;
    xy_with_del_cp.splice(1 + 2*del_cpindex, 2); // +1 because first element is shape_id
    this.d.metadata_update_xy(this.vid, mid, xy_with_del_cp).then( function(ok) {
      this._creg_draw_all();
      _via_util_msg_show('Deleted vertex at index [' + cpindex + ']');
      ok_callback(ok.mid);
    }.bind(this), function(err) {
      console.warn(err);
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._metadata_polygon_add_vertex = function(mindex, cpindex, cx, cy) {
  return new Promise( function(ok_callback, err_callback) {
    var mid = this.selected_mid_list[mindex];
    var x = cx * this.cscale;
    var y = cy * this.cscale;
    var xy_with_added_cp = this.d.store.metadata[mid].xy.slice();
    var new_cp_index = cpindex + 1; // add to the next position
    xy_with_added_cp.splice(1 + 2*new_cp_index, 0, x, y); // +1 because first element is shape_id
    this.d.metadata_update_xy(this.vid, mid, xy_with_added_cp).then( function(ok) {
      this._creg_draw_all();
      _via_util_msg_show('Added vertex after existing vertex at index [' + cpindex + ']');
      ok_callback(ok.mid);
    }.bind(this), function(err) {
      console.warn(err);
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._metadata_resize_region = function(mindex, cpindex, cx, cy) {
  return new Promise( function(ok_callback, err_callback) {
    var mid = this.selected_mid_list[mindex];
    var x = cx * this.cscale;
    var y = cy * this.cscale;
    var moved_xy = this._creg_move_control_point(this.d.store.metadata[mid].xy,
                                                 cpindex, x, y);
    this.d.metadata_update_xy(this.vid, mid, moved_xy).then( function(ok) {
      this._creg_draw_all();
      ok_callback(ok.mid);
    }.bind(this), function(err) {
      console.warn(err);
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._metadata_move_region = function(mid_list, cdx, cdy) {
  return new Promise( function(ok_callback, err_callback) {
    var mid, shape_id;
    var dx = cdx * this.cscale;
    var dy = cdy * this.cscale;
    var promise_list = [];
    var n = mid_list.length;
    for ( var i = 0; i < n; ++i ) {
      mid = mid_list[i];
      var new_xy  = this._metadata_move_xy(this.d.store.metadata[mid].xy, dx, dy);
      promise_list.push( this.d.metadata_update_xy(this.vid, mid, new_xy) );
    }

    Promise.all( promise_list ).then( function(ok) {
      this._smetadata_set_position();
      this._creg_draw_all();
      ok_callback();
    }.bind(this), function(err) {
      console.log(err)
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._metadata_add = function(region_shape, canvas_input_pts) {
  return new Promise( function(ok_callback, err_callback) {
    var file_input_pts = this._rinput_pts_canvas_to_file(canvas_input_pts);
    var xy = this._metadata_pts_to_xy(region_shape, file_input_pts);
    var z = [];
    if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
      z[0] = this.file_html_element.currentTime;
    }
    // set default attributes
    var av = this._metadata_get_default_attribute_values();

    // if a temporal segment is selected, we add this to the metadata
    if ( this.va.temporal_segmenter ) {
      if ( this.va.temporal_segmenter.selected_gindex !== -1 ) {
        av[ this.va.temporal_segmenter.groupby_aid ] = this.va.temporal_segmenter.selected_gid;
      }
    }

    this.d.metadata_add(this.vid, z, xy, av).then( function(ok) {
      ok_callback(ok.mid);
    }.bind(this), function(err) {
      console.warn(err);
      err_callback();
    }.bind(this));
  }.bind(this));
}

_via_file_annotator.prototype._metadata_get_default_attribute_values = function() {
  var av = {};
  var aid_list = this.d._cache_get_attribute_group(['FILE1_Z1_XY1',
                                                    'FILE1_Z0_XY1']);

  if ( Object.keys(aid_list).length) {
    for ( var aindex in aid_list ) {
      var aid = aid_list[aindex];
      if ( this.d.store.attribute[aid].hasOwnProperty('default_option_id') ) {
        if ( this.d.store.attribute[aid]['default_option_id'] !== '' ) {
          av[aid] = this.d.store.attribute[aid]['default_option_id'];
        }
      }
    }
  }
  return av;
}

_via_file_annotator.prototype._metadata_xy_to_creg = function(vid, mid) {
  var cxy = this.d.store.metadata[mid].xy.slice(0);
  var n = cxy.length;
  for ( var i = 1; i < n; ++i ) {
    cxy[i] = this.d.store.metadata[mid].xy[i] * this.fscale;
  }
  return cxy;
}

_via_file_annotator.prototype._metadata_move_xy = function(xy0, dx, dy) {
  var xy = xy0.slice(0);
  var shape_id = xy[0];
  switch(shape_id) {
  case _VIA_RSHAPE.POINT:
  case _VIA_RSHAPE.RECTANGLE:
  case _VIA_RSHAPE.CIRCLE:
  case _VIA_RSHAPE.ELLIPSE:
    xy[1] = xy[1] + dx;
    xy[2] = xy[2] + dy;
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
  case _VIA_RSHAPE.EXTREME_CIRCLE:
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYLINE:
  case _VIA_RSHAPE.POLYGON:
    var n = xy.length;
    for ( var i = 1; i < n; i = i+2 ) {
      xy[i]   = xy[i]   + dx;
      xy[i+1] = xy[i+1] + dy;
    }
    break;
  case _VIA_RSHAPE.FILE:
    break;
  }
  return xy;
}

_via_file_annotator.prototype._metadata_pts_to_xy = function(shape_id, pts) {
  var xy = [shape_id];
  switch(shape_id) {
  case _VIA_RSHAPE.POINT:
    xy.push( pts[0], pts[1] );
    break;
  case _VIA_RSHAPE.RECTANGLE:
    var d = this._metadata_pts_to_xy_rect(pts);
    xy.push( d[0], d[1], d[2], d[3] );
    break;
  case _VIA_RSHAPE.CIRCLE:
    xy.push( pts[0], pts[1] );
    var dx = pts[2] - pts[0];
    var dy = pts[3] - pts[1];
    var r = Math.sqrt( dx*dx + dy*dy ); // radius
    xy.push(r);
    break;
  case _VIA_RSHAPE.ELLIPSE:
    xy.push( pts[0], pts[1] );
    xy.push( Math.abs(pts[2] - pts[0]) ); // rx
    xy.push( Math.abs(pts[3] - pts[1]) ); // ry
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
  case _VIA_RSHAPE.EXTREME_CIRCLE:
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYLINE:
  case _VIA_RSHAPE.POLYGON:
    var n = pts.length;
    for ( var i = 0; i < n; ++i ) {
      xy.push( pts[i] );
    }
    break;
  case _VIA_RSHAPE.FILE:
    break;
  }
  return xy;
}

_via_file_annotator.prototype._metadata_pts_to_xy_rect = function(pts) {
  var d = [];
  var x2, y2;
  if ( pts[0] < pts[2] ) {
    d[0] = pts[0];
    x2   = pts[2];
  } else {
    d[0] = pts[2];
    x2   = pts[0];
  }
  if ( pts[1] < pts[3] ) {
    d[1] = pts[1];
    y2   = pts[3];
  } else {
    d[1] = pts[3];
    y2   = pts[1];
  }
  d[2] = x2 - d[0]; // width
  d[3] = y2 - d[1]; // height
  return d;
}

//
// canvas region maintainers
//
_via_file_annotator.prototype._creg_update = function(vid) {
  var mid;
  for ( var mindex in this.d.cache.mid_list[this.vid] ) {
    mid = this.d.cache.mid_list[this.vid][mindex];
    if ( this.d.store.metadata[mid].z.length === 0 &&
         this.d.store.metadata[mid].xy.length !== 0
       ) {
      this.creg[mid] = this._metadata_xy_to_creg(this.vid, mid);
    }
  }
}

_via_file_annotator.prototype._on_event_edit_current_frame_regions = function(data, event_payload) {
  this._creg_show_current_frame_regions();
}

_via_file_annotator.prototype._on_event_edit_frame_regions = function(data, event_payload) {
  this.creg = {};
  var mid;
  for ( var mindex in event_payload.mid_list ) {
    mid = event_payload.mid_list[mindex];
    this.creg[mid] = this._metadata_xy_to_creg(this.vid, mid);
  }
  this._creg_draw_all();
}

_via_file_annotator.prototype._creg_show_current_frame_regions = function() {
  this._creg_add_current_frame_regions(this.vid);
  this._creg_draw_all();
}

_via_file_annotator.prototype._creg_add_current_frame_regions = function(vid) {
  this.creg = {};

  var t = this.file_html_element.currentTime;
  var mid;
  for ( var mindex in this.d.cache.mid_list[vid] ) {
    mid = this.d.cache.mid_list[vid][mindex];
    if ( this.d.store.metadata[mid].xy.length !== 0 ) {
      if ( this.d.store.metadata[mid].z.length === 0 ) {
        this.creg[mid] = this._metadata_xy_to_creg(vid, mid);
      } else {
        if ( Math.abs(this.d.store.metadata[mid].z[0] - t) < this.conf.SPATIAL_REGION_TIME_TOL ) {
          this.creg[mid] = this._metadata_xy_to_creg(vid, mid);
        }
      }
    }
  }
}

_via_file_annotator.prototype._creg_add = function(vid, mid) {
  this.creg[mid] = this._metadata_xy_to_creg(vid, mid);
}

_via_file_annotator.prototype._creg_clear = function() {
  this.rshapectx.clearRect(0, 0, this.rshape_canvas.width, this.rshape_canvas.height);
}

_via_file_annotator.prototype._creg_draw_all = function() {
  this._creg_clear();

  if ( this.d.store.config.ui['spatial_region_label_attribute_id'] === '' ) {
    for ( var mid in this.creg ) {
      this._creg_draw(mid);
    }
  } else {
    for ( var mid in this.creg ) {
      this._creg_draw(mid);
      this._creg_draw_label(mid);
    }
  }

  // file label: used for image pair annotation
  if ( this.file_label.length !== 0 ) {
    this._creg_draw_file_label();
  }
}

_via_file_annotator.prototype._creg_draw_file_label = function() {
  this.rshapectx.fillStyle = 'yellow';
  this.rshapectx.font = '16px mono';
  var label_width = this.rshapectx.measureText(this.file_label).width;
  this.rshapectx.fillText(this.file_label, this.rshape_canvas.width/2 - label_width/2, 20);
}

_via_file_annotator.prototype._creg_draw = function(mid) {
  if(this.show_region_shape) {
    var is_selected = this.selected_mid_list.includes(mid);
    this._draw(this.rshapectx, this.creg[mid], is_selected)
  }
}

_via_file_annotator.prototype._creg_draw_label = function(mid) {
  if(!this.show_region_label) {
    return;
  }
  
  if ( this.d.store.metadata[mid].av.hasOwnProperty(this.d.store.config.ui['spatial_region_label_attribute_id']) ) {
    var lx = this.creg[mid][1];
    var ly = this.creg[mid][2];

    var label = '';
    switch(this.d.store.attribute[this.d.store.config.ui['spatial_region_label_attribute_id']].type) {
    case _VIA_ATTRIBUTE_TYPE.RADIO:
    case _VIA_ATTRIBUTE_TYPE.SELECT:
    case _VIA_ATTRIBUTE_TYPE.CHECKBOX:
      var option_id = this.d.store.metadata[mid].av[this.d.store.config.ui['spatial_region_label_attribute_id']];
      label = this.d.store.attribute[this.d.store.config.ui['spatial_region_label_attribute_id']].options[option_id];
      break;
    case _VIA_ATTRIBUTE_TYPE.TEXT:
      label = this.d.store.metadata[mid].av[this.d.store.config.ui['spatial_region_label_attribute_id']];
      break;
    }

    if ( label === '' ) {
      return;
    }
    if ( label.length > _VIA_SPATIAL_REGION_LABEL_MAXLENGTH ) {
      label = label.substr(0, _VIA_SPATIAL_REGION_LABEL_MAXLENGTH) + '.';
    }

    //this.rshapectx.shadowColor = 'transparent';
    this.rshapectx.font = _VIA_SPATIAL_REGION_LABEL_FONT;
    var cw = this.rshapectx.measureText('M').width;
    var ch = 1.8 * cw;
    var bgnd_rect_width = cw * (label.length);
    if ( label.length === 1 ) {
      bgnd_rect_width = 2*bgnd_rect_width;
    }

    // draw background rectangle
    this.rshapectx.fillStyle = 'black';
    this.rshapectx.fillRect(Math.floor(lx),
                            Math.floor(ly - 1.1*ch),
                            Math.floor(bgnd_rect_width),
                            Math.floor(ch));
    // then, draw text over this background rectangle
    this.rshapectx.fillStyle = 'yellow';
    this.rshapectx.fillText(label,
                            Math.floor(lx + 0.5*cw),
                            Math.floor(ly - 0.35*ch));
  }
}

_via_file_annotator.prototype._creg_is_inside = function(xy, cx, cy, tolerance) {
  var shape_id = xy[0];
  var is_inside = false;
  switch( shape_id ) {
  case _VIA_RSHAPE.POINT:
    var dx = Math.abs(xy[1] - cx);
    var dy = Math.abs(xy[2] - cy);
    if ( dx <= tolerance && dy <= tolerance ) {
      is_inside = true;
    }
    break;
  case _VIA_RSHAPE.RECTANGLE:
    if ( cx > xy[1] && cx < (xy[1] + xy[3]) ) {
      if ( cy > xy[2] && cy < (xy[2] + xy[4]) ) {
        is_inside = true;
      }
    }
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
    var rshape = this._extreme_to_rshape(xy, shape_id);
    if ( cx > rshape[1] && cx < (rshape[1] + rshape[3]) ) {
      if ( cy > rshape[2] && cy < (rshape[2] + rshape[4]) ) {
        is_inside = true;
      }
    }
    break;
  case _VIA_RSHAPE.CIRCLE:
    var dx = Math.abs(xy[1] - cx);
    var dy = Math.abs(xy[2] - cy);
    if ( Math.sqrt((dx * dx) + (dy * dy)) < xy[3] ) {
      is_inside = true;
    }
    break;
  case _VIA_RSHAPE.EXTREME_CIRCLE:
    var rshape = this._extreme_to_rshape(xy, shape_id);
    var dx = Math.abs(rshape[1] - cx);
    var dy = Math.abs(rshape[2] - cy);
    if ( Math.sqrt((dx * dx) + (dy * dy)) < rshape[3] ) {
      is_inside = true;
    }
    break;
  case _VIA_RSHAPE.ELLIPSE:
    var dx = Math.abs(xy[1] - cx);
    var dy = Math.abs(xy[2] - cy);
    var inv_rx2 = 1 / ( xy[3] * xy[3] );
    var inv_ry2 = 1 / ( xy[4] * xy[4] );
    if ( ( ( dx * dx * inv_rx2 ) + ( dy * dy * inv_ry2 ) ) < 1 ) {
      is_inside = true;
    }
    break;
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYLINE:
    var dx = Math.abs(xy[1] - cx);
    var dy = Math.abs(xy[2] - cy);
    if ( dx <= this.conf.FIRST_VERTEX_CLICK_TOL &&
         dy <= this.conf.FIRST_VERTEX_CLICK_TOL ) {
      is_inside = true;
    } else {
      if ( this._creg_is_inside_polyline(xy, cx, cy, tolerance) !== 0 ) {
        is_inside = true;
      }
    }
    break;
  case _VIA_RSHAPE.POLYGON:
    var dx = Math.abs(xy[1] - cx);
    var dy = Math.abs(xy[2] - cy);
    if ( dx <= this.conf.FIRST_VERTEX_CLICK_TOL &&
         dy <= this.conf.FIRST_VERTEX_CLICK_TOL ) {
      is_inside = true;
    } else {
      if ( this._creg_is_inside_polygon(xy, cx, cy) !== 0 ) {
        is_inside = true;
      }
    }
    break;
  default:
    console.warn('_via_file_annotator._draw() : shape_id=' + shape_id + ' not implemented');
  }
  return is_inside;
}

// returns 0 when (px,py) is far from the polyline
// based on algorithm described at
// https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
_via_file_annotator.prototype._creg_is_inside_polyline = function (xy_pts, px, py, tolerance) {
  var xy = xy_pts.slice(0);
  if ( xy.length === 0 || xy.length === 1 ) {
    return 0;
  }
  var mn = tolerance * 10.0; // a relatively large number
  // loop through all points of the polyline
  for ( var i = 1; i < xy.length; i = i + 2 ) {   // line from V[i] to  V[i+1]
    var ldx = xy[i+2] - xy[i];
    var ldy = xy[i+3] - xy[i+1];
    var line_seg_length_sq = ldx * ldx + ldy * ldy;
    var pdx = px - xy[i];
    var pdy = py - xy[i+1];
    var dst = mn;
    if ( line_seg_length_sq === 0 ) {
      dst = Math.sqrt( pdx * pdx + pdy * pdy );
    }
    else {
      var pld = (pdx * ldx + pdy * ldy) / line_seg_length_sq;
      var t = Math.max( 0, Math.min( 1, pld ) );  // constrain line to this segment
      var tdx = px - ( xy[i] + t * ldx );
      var tdy = py - ( xy[i+1] + t * ldy );
      dst = Math.sqrt( tdx * tdx + tdy * tdy );
    }
    if ( dst < mn ) {
      mn = dst;
    }
  }
  if ( mn < tolerance ) {
    return 1;
  }
  else {
    return 0;
  }
}

// returns 0 when (px,py) is outside the polygon
// source: http://geomalgorithms.com/a03-_inclusion.html
_via_file_annotator.prototype._creg_is_inside_polygon = function (xy_pts, px, py) {
  var xy = xy_pts.slice(0);
  if ( xy.length === 0 || xy.length === 1 ) {
    return 0;
  }
  xy.push(xy[1], xy[2]); // close the loop

  var wn = 0;    // the  winding number counter
  // loop through all edges of the polygon
  for ( var i = 1; i < xy.length; i = i + 2 ) {   // edge from V[i] to  V[i+1]
    var is_left_value = this._creg_is_left( xy[i], xy[i+1],
                                            xy[i+2], xy[i+3],
                                            px, py);

    if (xy[i + 1] <= py) {
      if (xy[i+3]  > py && is_left_value > 0) {
        ++wn;
      }
    }
    else {
      if (xy[i+3]  <= py && is_left_value < 0) {
        --wn;
      }
    }
  }
  if ( wn === 0 ) {
    return 0;
  }
  else {
    return 1;
  }
}

// >0 if (x2,y2) lies on the left side of line joining (x0,y0) and (x1,y1)
// =0 if (x2,y2) lies on the line joining (x0,y0) and (x1,y1)
// >0 if (x2,y2) lies on the right side of line joining (x0,y0) and (x1,y1)
// source: http://geomalgorithms.com/a03-_inclusion.html
_via_file_annotator.prototype._creg_is_left = function (x0, y0, x1, y1, x2, y2) {
  return ( ((x1 - x0) * (y2 - y0))  - ((x2 -  x0) * (y1 - y0)) );
}

_via_file_annotator.prototype._creg_move_control_point = function(xy0, cpindex, new_x, new_y) {
  var xy = xy0.slice(0);
  var shape_id = xy[0];
  switch( shape_id ) {
  case _VIA_RSHAPE.POINT:
    break;
  case _VIA_RSHAPE.RECTANGLE:
    switch(cpindex) {
    case 1:
      xy[2] = new_y;
      xy[4] = xy0[4] + xy0[2] - new_y;
      break;
    case 2:
      xy[3] = new_x - xy0[1];
      break;
    case 3:
      xy[4] = new_y - xy0[2];
      break;
    case 4:
      xy[1] = new_x;
      xy[3] = xy0[3] + xy0[1] - new_x;
      break;
    case 5:
      xy[2] = new_y;
      xy[3] = new_x - xy0[1];
      xy[4] = xy0[4] + xy0[2] - new_y;
      break;
    case 6:
      xy[3] = new_x - xy0[1];
      xy[4] = new_y - xy0[2];
      break;
    case 7:
      xy[1] = new_x;
      xy[3] = xy0[3] + xy0[1] - new_x;
      xy[4] = new_y - xy0[2];
      break;
    case 8:
      xy[3] = xy0[3] + xy0[1] - new_x;
      xy[4] = xy0[4] + xy0[2] - new_y;
      xy[1] = new_x;
      xy[2] = new_y;
      break;
    }
    break;
  case _VIA_RSHAPE.CIRCLE:
    var new_dx = new_x - xy0[1];
    var new_dy = new_y - xy0[2];
    xy[3] = Math.sqrt( new_dx*new_dx + new_dy*new_dy );
    break;
  case _VIA_RSHAPE.ELLIPSE:
    switch(cpindex) {
    case 1:
      xy[4] = Math.abs(new_y - xy0[2]);
      break;
    case 2:
      xy[3] = Math.abs(new_x - xy0[1]);
      break;
    }
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
  case _VIA_RSHAPE.EXTREME_CIRCLE:
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYGON:
  case _VIA_RSHAPE.POLYLINE:
    xy[ 2*cpindex - 1 ] = new_x;
    xy[ 2*cpindex ]     = new_y;
    break;
  }
  return xy;
}

_via_file_annotator.prototype._creg_get_control_points = function(xy) {
  var shape_id = xy[0];
  switch( shape_id ) {
  case _VIA_RSHAPE.POINT:
    return [ xy[0] ];
    break;
  case _VIA_RSHAPE.RECTANGLE:
    return [
      shape_id,
      xy[1]+xy[3]/2, xy[2],
      xy[1]+xy[3]  , xy[2]+xy[4]/2,
      xy[1]+xy[3]/2, xy[2]+xy[4],
      xy[1]        , xy[2]+xy[4]/2,
      xy[1]+xy[3]  , xy[2],
      xy[1]+xy[3]  , xy[2]+xy[4],
      xy[1]        , xy[2]+xy[4],
      xy[1]        , xy[2],
    ];
    break;
  case _VIA_RSHAPE.CIRCLE:
    return [
      shape_id,
      xy[1], xy[2] - xy[3],
    ]
    break;
  case _VIA_RSHAPE.ELLIPSE:
    return [
      shape_id,
      xy[1]        , xy[2] - xy[4],
      xy[1] + xy[3], xy[2],
    ]
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
  case _VIA_RSHAPE.EXTREME_CIRCLE:
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYGON:
  case _VIA_RSHAPE.POLYLINE:
    return xy;
    break;
  }
  return [];
}

_via_file_annotator.prototype._creg_is_near_a_point = function(px, py, x, y, tolerance) {
  var dx = Math.abs(x - px);
  var dy = Math.abs(y - py);
  if ( dx <= tolerance && dy <= tolerance ) {
    return true;
  } else {
    return false;
  }
}

_via_file_annotator.prototype._creg_is_on_control_point = function(xy, cx, cy, tolerance) {
  var cp = this._creg_get_control_points(xy); // cp[0] = shape_id
  var n = cp.length;
  for ( var i = 1; i < n; i = i + 2 ) {
    if ( this._creg_is_near_a_point(cp[i], cp[i+1], cx, cy, tolerance) ) {
      return (i+1)/2; // to convert xy index to control point index
    }
  }
  return -1;
}

_via_file_annotator.prototype._creg_is_on_polygon_edge = function(xy, cx, cy, tolerance) {
  var cp = this._creg_get_control_points(xy); // cp[0] = shape_id
  var n = cp.length;
  var distance_to_edge = [];
  for ( var i = 1; i < n - 2; i = i + 2 ) {
    var di = this.dist_to_line(cx, cy, cp[i], cp[i+1], cp[i+2], cp[i+3]);
    distance_to_edge.push(di);
  }
  var shape_id = cp[0];
  if(shape_id === _VIA_RSHAPE.POLYGON) {
    // add closing edge
    var di = this.dist_to_line(cx, cy, cp[n-2], cp[n-1], cp[1], cp[2]);
    distance_to_edge.push(di);
  }

  var smallest_value = distance_to_edge[0];
  var smallest_index = 0;
  n = distance_to_edge.length;
  for ( i = 1; i < n; ++i ) {
    if ( distance_to_edge[i] < smallest_value ) {
      smallest_value = distance_to_edge[i];
      smallest_index = i;
    }
  }
  if ( smallest_value < tolerance ) {
    return smallest_index;
  } else {
    return -1;
  }
}

_via_file_annotator.prototype.dist_to_line = function(x, y, x1, y1, x2, y2) {
  if ( this.is_point_inside_bounding_box(x, y, x1, y1, x2, y2) ) {
    var dy = y2 - y1;
    var dx = x2 - x1;
    var nr = Math.abs( dy*x - dx*y + x2*y1 - y2*x1 );
    var dr = Math.sqrt( dx*dx + dy*dy );
    var dist = nr / dr;
    return Math.round(dist);
  } else {
    return Number.MAX_SAFE_INTEGER;
  }
}

_via_file_annotator.prototype.is_point_inside_bounding_box = function(x, y, x1, y1, x2, y2) {
  // ensure that (x1,y1) is top left and (x2,y2) is bottom right corner of rectangle
  var rect = {};
  if( x1 < x2 ) {
    rect.x1 = x1;
    rect.x2 = x2;
  } else {
    rect.x1 = x2;
    rect.x2 = x1;
  }
  if ( y1 < y2 ) {
    rect.y1 = y1;
    rect.y2 = y2;
  } else {
    rect.y1 = y2;
    rect.y2 = y1;
  }

  if ( x >= rect.x1 && x <= rect.x2 && y >= rect.y1 && y <= rect.y2 ) {
    return true;
  } else {
    return false;
  }
}

_via_file_annotator.prototype._creg_is_on_sel_region_cp  = function(cx, cy, tolerance) {
  var n = this.selected_mid_list.length;
  var mid;
  var sel_region_cp = [-1, -1, '']; // [ metadata_index, control_point_index, type = {cp, edge} ]
  for ( var i = 0; i < n; ++i ) {
    mid = this.selected_mid_list[i];
    var cp_index = this._creg_is_on_control_point(this.creg[mid], cx, cy, tolerance);
    // is mousedown on region control point?
    if ( cp_index !== -1 ) {
      sel_region_cp = [i, cp_index, 'cp'];
      break;
    } else {
      // for polyline and polygon, check if it is on the edge
      var shape_id = this.creg[mid][0];
      if(shape_id === _VIA_RSHAPE.POLYLINE || shape_id === _VIA_RSHAPE.POLYGON) {
        var edge_index = this._creg_is_on_polygon_edge(this.creg[mid], cx, cy, tolerance);
        if(edge_index !== -1) {
          sel_region_cp = [i, edge_index, 'edge'];
          break;
        }
      }
    }
  }
  return sel_region_cp;
}

_via_file_annotator.prototype._creg_select_one = function(mid) {
  this.selected_mid_list = [mid];
}

_via_file_annotator.prototype._creg_select = function(mid) {
  this.selected_mid_list.push(mid);
}

_via_file_annotator.prototype._creg_select_multiple = function(mid_list) {
  var n = mid_list.length;
  if ( n > 0 ) {
    for ( var i = 0; i < n; ++i ) {
      this.selected_mid_list.push( mid_list[i] );
    }
  }
}

_via_file_annotator.prototype._creg_select_toggle = function(mid_list) {
  var n = mid_list.length;
  if ( n > 0 ) {
    var mindex;
    for ( var i = 0; i < n; ++i ) {
      mindex = this.selected_mid_list.indexOf( mid_list[i] );
      if ( mindex === -1 ) {
        // add to selection
        this.selected_mid_list.push( mid_list[i] );
      } else {
        // remove from selection
        this.selected_mid_list.splice(mindex, 1 );
      }
    }
  }
}

_via_file_annotator.prototype._creg_select_none = function() {
  this.selected_mid_list = [];
}

_via_file_annotator.prototype._creg_select_all = function() {
  this.selected_mid_list = Object.keys(this.creg);
}

_via_file_annotator.prototype._creg_del_sel_regions = function() {
  this.d.metadata_delete_bulk( this.vid, this.selected_mid_list, true ).then( function(ok) {
    this._creg_select_none();
    this._smetadata_hide();
    this.user_input_pts = [];
    this._state_set( _VIA_RINPUT_STATE.IDLE );
  }.bind(this), function(err) {
    console.log(err);
  }.bind(this));
}

_via_file_annotator.prototype._creg_edge_minmax_dist_to_point = function(xy, px, py) {
  var shape_id = xy[0];
  var edge_pts = [];
  switch( shape_id ) {
  case _VIA_RSHAPE.POINT:
    edge_pts = [ xy[0], xy[1] ];
    break;
  case _VIA_RSHAPE.RECTANGLE:
    var w2 = xy[3] / 2.0;
    var h2 = xy[4] / 2.0;
    edge_pts = [xy[1], xy[2], xy[1] + w2, xy[2], xy[1] + xy[3], xy[2],
                xy[1] + xy[3], xy[2] + h2, xy[1] + xy[3], xy[2] + xy[4],
                xy[1] + w2, xy[2] + xy[4], xy[1], xy[2] + xy[4], xy[1], xy[2] + h2 ];
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
    var exy = this._extreme_to_rshape(xy, shape_id);
    var w2 = exy[3] / 2.0;
    var h2 = exy[4] / 2.0;
    edge_pts = [exy[1], exy[2], exy[1] + w2, exy[2], exy[1] + exy[3], exy[2],
                exy[1] + exy[3], exy[2] + h2, exy[1] + exy[3], exy[2] + exy[4],
                exy[1] + w2, exy[2] + h2, exy[1], exy[2] + exy[4], exy[1], exy[2] + h2 ];
    break;
  case _VIA_RSHAPE.CIRCLE:
    edge_pts = [xy[1] + xy[3], xy[2], xy[1], xy[2] - xy[3],
                xy[1] - xy[3], xy[2], xy[1], xy[2] + xy[3] ]
    break;
  case _VIA_RSHAPE.EXTREME_CIRCLE:
    var exy = this._extreme_to_rshape(xy, shape_id);
    edge_pts = [exy[1] + exy[3], exy[2], exy[1], exy[2] - exy[3],
                exy[1] - exy[3], exy[2], exy[1], exy[2] + exy[3] ]
    break;
  case _VIA_RSHAPE.ELLIPSE:
    edge_pts = [xy[1] + xy[3], xy[2], xy[1], xy[2] - xy[4],
                xy[1] - xy[3], xy[2] - xy[4], xy[1], xy[2] + xy[4] ];
    break;
  case _VIA_RSHAPE.LINE:
    var w2 = (xy[3] + xy[1]) / 2.0;
    var h2 = (xy[4] + xy[2]) / 2.0;
    edge_pts = [ xy[1], xy[2], xy[1] + w2, xy[2] + h2, xy[3], xy[4] ];
    break;
  case _VIA_RSHAPE.POLYGON:
  case _VIA_RSHAPE.POLYLINE:
    edge_pts = xy.slice(1); // discard shape_id;
    break;
  default:
    console.warn('_via_file_annotator._draw() : shape_id=' + shape_id + ' not implemented');
  }
  var dist_minmax = [+Infinity, -Infinity];
  var dist, dx, dy;
  for(var i=0; i<edge_pts.length; i=i+2) {
    dx = Math.abs(edge_pts[i] - px);
    dy = Math.abs(edge_pts[i+1] - py);
    dist = Math.sqrt(dx*dx + dy*dy);
    if(dist < dist_minmax[0]) {
      dist_minmax[0] = dist;
    }
    if(dist > dist_minmax[1]) {
      dist_minmax[1] = dist;
    }
  }
  return dist_minmax;
}

//
// external event listener
//
_via_file_annotator.prototype._on_event_metadata_add = function(data, event_payload) {
  var vid = event_payload.vid;
  var mid = event_payload.mid;
  if ( this.vid === vid &&
       this.d.store.metadata[mid].xy.length !== 0 ) {
    // spatial region was added, but added to what?
    if ( this.d.store.metadata[mid].z.length === 1 ) {
      // spatial region in a video frame was added
      this.va.temporal_segmenter._tmetadata_boundary_add_spatial_mid(mid);
      this._creg_add(vid, mid);
      this._creg_draw_all();
      this.va.temporal_segmenter._tmetadata_gtimeline_draw();
    } else {
      // spatial region in an image was added
      this._creg_add(vid, mid);
      this._creg_draw_all();
    }
  }
}

_via_file_annotator.prototype._on_event_metadata_delete_bulk = function(data, event_payload) {
  var vid = event_payload.vid;
  var mid_list = event_payload.mid_list;
  for ( var mindex in mid_list ) {
    var mid = mid_list[mindex];
    if ( this.vid === vid && this.va.temporal_segmenter ) {
      this.va.temporal_segmenter._tmetadata_boundary_del_spatial_mid(mid);
    }
  }
  this._creg_add_current_frame_regions(this.vid);
  this._creg_draw_all();

  if(this.va.temporal_segmenter) {
    this.va.temporal_segmenter._tmetadata_gtimeline_draw();
  }
}

_via_file_annotator.prototype._on_event_metadata_update = function(data, event_payload) {
  var vid = event_payload.vid;
  var mid = event_payload.mid;
  if ( this.vid === vid &&
       this.d.store.metadata[mid].xy.length
     ) {
    this._creg_add(vid, mid);
    this._creg_draw_all();
  }
}

_via_file_annotator.prototype._on_event_view_update = function(data, event_payload) {
  var vid = event_payload.vid;

  if ( this.vid === vid ) {
    this._creg_reload();
  }
}

_via_file_annotator.prototype._on_event_attribute_update = function(data, event_payload) {
  this._fmetadata_show();
}

_via_file_annotator.prototype._on_event_attribute_del = function(data, event_payload) {
  this._fmetadata_show();
}

//
// temp. regions
//
_via_file_annotator.prototype._tmpreg_draw_region = function(shape_id, pts) {
  var xy = this._metadata_pts_to_xy(shape_id, pts);

  this._draw(this.temprctx, xy);
}

_via_file_annotator.prototype._tmpreg_draw_crosshair = function(cx, cy) {
  // draw cross hair in complementary colours
  this.temprctx.lineWidth = 1;
  this.temprctx.strokeStyle = this.conf.CROSSHAIR_COLOR1;
  this.temprctx.beginPath();
  this.temprctx.moveTo(0, cy - 0.5);
  this.temprctx.lineTo(this.tempr_canvas.width, cy - 0.5);
  this.temprctx.moveTo(cx - 0.5, 0);
  this.temprctx.lineTo(cx - 0.5, this.tempr_canvas.height);
  this.temprctx.stroke();

  this.temprctx.strokeStyle = this.conf.CROSSHAIR_COLOR2;
  this.temprctx.beginPath();
  this.temprctx.moveTo(0, cy + 0.5);
  this.temprctx.lineTo(this.tempr_canvas.width, cy + 0.5);
  this.temprctx.moveTo(cx + 0.5, 0);
  this.temprctx.lineTo(cx + 0.5, this.tempr_canvas.height);
  this.temprctx.stroke();
}

_via_file_annotator.prototype._tmpreg_move_sel_regions = function(dx, dy) {
  var mid, mindex;
  for ( mindex in this.selected_mid_list ) {
    mid = this.selected_mid_list[mindex];
    var new_cxy = this._metadata_move_xy(this.creg[mid], dx, dy);
    this._draw(this.temprctx, new_cxy, false);
  }
}

_via_file_annotator.prototype._tmpreg_move_sel_region_cp = function(mindex, cpindex, cx, cy) {
  var mid = this.selected_mid_list[mindex];
  var moved_cxy = this._creg_move_control_point(this.creg[mid], cpindex, cx, cy);
  this._draw(this.temprctx, moved_cxy, false);
}

_via_file_annotator.prototype._tmpreg_clear = function() {
  this.temprctx.clearRect(0, 0, this.tempr_canvas.width, this.tempr_canvas.height);
  if(this._is_magnifier_enabled) {
    this.magnifier_tempr_ctx.clearRect(0, 0, this.magnifier_canvas_width, this.magnifier_canvas_height);
  }
}

//
// region draw routines
//
// Note: xy = [shape_id, x0, y0, x1, y1, ..., xk, yk]
_via_file_annotator.prototype._draw = function(ctx, xy, is_selected) {
  var shape_id = xy[0];
  switch( shape_id ) {
  case _VIA_RSHAPE.POINT:
    this._draw_point_region(ctx, xy[1], xy[2], is_selected );
    break;
  case _VIA_RSHAPE.RECTANGLE:
    this._draw_rect_region(ctx, xy[1], xy[2], xy[3], xy[4], is_selected );
    break;
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
    this._draw_extreme_rectangle_region(ctx, xy, is_selected );
    break;
  case _VIA_RSHAPE.CIRCLE:
    this._draw_circle_region(ctx, xy[1], xy[2], xy[3], is_selected );
    break;
  case _VIA_RSHAPE.EXTREME_CIRCLE:
    this._draw_extreme_circle_region(ctx, xy, is_selected );
    break;
  case _VIA_RSHAPE.ELLIPSE:
    this._draw_ellipse_region(ctx, xy[1], xy[2], xy[3], xy[4], is_selected );
    break;
  case _VIA_RSHAPE.EXTREME_BOX:
    this._draw_extreme_box_region(ctx, xy, is_selected);
    break;
  case _VIA_RSHAPE.LINE:
  case _VIA_RSHAPE.POLYGON:
  case _VIA_RSHAPE.POLYLINE:
    this._draw_polygon_region(ctx, xy, is_selected, shape_id);
    break;
  default:
    console.warn('_via_file_annotator._draw() : shape_id=' + shape_id + ' not implemented');
  }

  if ( is_selected ) {
    var cp = this._creg_get_control_points(xy); // cp[0] = shape_id
    var n = cp.length;
    for ( var i = 1; i < n; i = i + 2 ) {
      this._draw_control_point(ctx, cp[i], cp[i+1]);
    }
  }

  if(this._is_magnifier_enabled) {
    var scaled_xy = [ xy[0] ];
    for(var i=1; i<xy.length; ++i) {
      scaled_xy[i] = xy[i] * this.magnifier_scale;
    }
    if(ctx.canvas.id === "region_shape") {
      this._draw(this.magnifier_rshape_ctx, scaled_xy, is_selected);
      if ( is_selected ) {
        var cp = this._creg_get_control_points(scaled_xy); // cp[0] = shape_id
        var n = cp.length;
        for ( var i = 1; i < n; i = i + 2 ) {
          this._draw_control_point(this.magnifier_rshape_ctx, cp[i], cp[i+1]);
        }
      }
    } else if(ctx.canvas.id === "region_input") {
      this._draw(this.magnifier_tempr_ctx, scaled_xy, is_selected);
    }
  }
}

_via_file_annotator.prototype._draw_point_region = function(ctx, cx, cy, is_selected) {
  if ( is_selected ) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_point(ctx, cx, cy, this.conf.REGION_POINT_RADIUS);
    ctx.stroke();
    ctx.strokeStyle = 'white';
    this._draw_point(ctx, cx, cy, this.conf.REGION_POINT_RADIUS + 2);
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_point(ctx, cx, cy, this.conf.REGION_POINT_RADIUS);
    ctx.stroke();
  }
}

_via_file_annotator.prototype._draw_point = function(ctx, cx, cy, r) {
  ctx.beginPath();
  ctx.arc(cx, cy, r, 0, 2*Math.PI, false);
  ctx.closePath();
}

_via_file_annotator.prototype._draw_rect_region = function(ctx, x, y, w, h, is_selected) {
  if (is_selected) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_rect(ctx, x, y, w, h);
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_rect(ctx, x, y, w, h);
    ctx.stroke();
  }
}

_via_file_annotator.prototype._draw_rect = function(ctx, x, y, w, h) {
  ctx.beginPath();
  ctx.moveTo(x  , y);
  ctx.lineTo(x+w, y);
  ctx.lineTo(x+w, y+h);
  ctx.lineTo(x  , y+h);
  ctx.closePath();
}

_via_file_annotator.prototype._draw_circle_region = function(ctx, cx, cy, r, is_selected) {
  if (is_selected) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_circle(ctx, cx, cy, r);
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_circle(ctx, cx, cy, r);
    ctx.stroke();
  }
}

_via_file_annotator.prototype._draw_circle = function(ctx, cx, cy, r) {
  ctx.beginPath();
  ctx.arc(cx, cy, r, 0, 2*Math.PI, false);
  ctx.closePath();
}

_via_file_annotator.prototype._draw_ellipse_region = function(ctx, cx, cy, rx, ry, is_selected) {
  if (is_selected) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_ellipse(ctx, cx, cy, rx, ry);
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_ellipse(ctx, cx, cy, rx, ry);
    ctx.stroke();
  }
}

_via_file_annotator.prototype._draw_ellipse = function(ctx, cx, cy, rx, ry) {
  ctx.save();
  ctx.beginPath();
  ctx.translate(cx-rx, cy-ry);
  ctx.scale(rx, ry);
  ctx.arc(1, 1, 1, 0, 2 * Math.PI, false);
  ctx.restore(); // restore to original state
  ctx.closePath();
}

_via_file_annotator.prototype._extreme_to_rshape = function(xy, shape_id) {
  var n = xy.length;
  switch(shape_id) {
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
    var x0 = xy[1];
    var y0 = xy[2];
    var x1 = xy[1];
    var y1 = xy[2];
    for ( var i = 3; i < n; i = i + 2 ) {
      if ( xy[i] < x0 ) {
        x0 = xy[i];
      }
      if ( xy[i] > x1 ) {
        x1 = xy[i];
      }
      if ( xy[i+1] < y0 ) {
        y0 = xy[i+1];
      }
      if ( xy[i+1] > y1 ) {
        y1 = xy[i+1];
      }
    }
    //console.log(JSON.stringify(xy) + ' : ' + JSON.stringify([x0,x1,y0,y1]));
    return [shape_id, x0, y0, (x1-x0), (y1-y0)];
    break;
  case _VIA_RSHAPE.EXTREME_CIRCLE:
    // let (cx,cy) be center of circle and r be the radius
    // assuming xy[x1,y1,x2,y2,x3,y3] contains three non-collinear points (x1,y1), (x2,y2) and (x3,y3)
    // distance between center and 1st point = d1 = (cx - xy[0])^2 + (cy - xy[1])^2
    // distance between center and 2nd point = d2 = (cx - xy[2])^2 + (cy - xy[3])^2
    // distance between center and 3rd point = d3 = (cx - xy[4])^2 + (cy - xy[5])^2
    // we solve for (cx,cy) using the equations: d1 = d2 = d3
    var xy2 = [0, Math.pow(xy[1],2), Math.pow(xy[2],2), Math.pow(xy[3],2), Math.pow(xy[4],2), Math.pow(xy[5],2), Math.pow(xy[6],2) ];
    var cy = ( ( ( xy2[3] + xy2[4] - xy2[1] - xy2[2]) * (xy[1] - xy[5]) ) - ( (xy2[1] + xy2[2] - xy2[5] - xy2[6]) * (xy[3] - xy[1]) ) ) / ( 2 * ( ( (xy[4] - xy[2]) * (xy[1] - xy[5]) ) - ( (xy[2] - xy[6]) * (xy[3] - xy[1]) ) ) );
    var cx = ( (xy2[1] + xy2[2] - xy2[5] - xy2[6]) - 2 * cy * (xy[2] - xy[6]) ) / ( 2 * (xy[1] - xy[5]) );
    var r = Math.sqrt( Math.pow(cx - xy[1], 2) + Math.pow(cy - xy[2], 2) );
    return [shape_id, cx, cy, r];
  default:
    return [];
  }
}

_via_file_annotator.prototype._draw_extreme_rectangle_region = function(ctx, xy, is_selected) {
  var n = xy.length;
  var ebox = this._extreme_to_rshape(xy, _VIA_RSHAPE.EXTREME_RECTANGLE);

  if ( is_selected ) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_rect(ctx, ebox[1], ebox[2], ebox[3], ebox[4]);
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_rect(ctx, ebox[1], ebox[2], ebox[3], ebox[4]);
    ctx.stroke();
  }

  // draw control points
  var cp = this._creg_get_control_points(xy); // cp[0] = shape_id
  var n = cp.length;
  for ( var i = 1; i < n; i = i + 2 ) {
    this._draw_control_point(ctx, cp[i], cp[i+1]);
  }
}

_via_file_annotator.prototype._draw_extreme_circle_region = function(ctx, xy, is_selected) {
  var n = xy.length;
  if ( n === 7 ) {
  var ebox = this._extreme_to_rshape(xy, _VIA_RSHAPE.EXTREME_CIRCLE);
    if ( is_selected ) {
      ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
      ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
      this._draw_circle(ctx, ebox[1], ebox[2], ebox[3]);
      ctx.stroke();

      ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
      ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
      ctx.fill();
      ctx.globalAlpha = 1.0;
    } else {
      ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
      ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
      this._draw_circle(ctx, ebox[1], ebox[2], ebox[3]);
      ctx.stroke();
    }
  }

  // draw control points
  var cp = this._creg_get_control_points(xy); // cp[0] = shape_id
  var n = cp.length;
  for ( var i = 1; i < n; i = i + 2 ) {
    this._draw_control_point(ctx, cp[i], cp[i+1]);
  }
}

_via_file_annotator.prototype._draw_polygon_region = function(ctx, pts, is_selected, shape_id) {
  if ( is_selected ) {
    ctx.strokeStyle = this.conf.SEL_REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.SEL_REGION_LINE_WIDTH;
    this._draw_polygon(ctx, pts);
    if ( shape_id === _VIA_RSHAPE.POLYGON ) {
      ctx.closePath();
    }
    ctx.stroke();

    ctx.fillStyle   = this.conf.SEL_REGION_FILL_COLOR;
    ctx.globalAlpha = this.conf.SEL_REGION_FILL_OPACITY;
    ctx.fill();
    ctx.globalAlpha = 1.0;
  } else {
    ctx.strokeStyle = this.conf.REGION_BOUNDARY_COLOR;
    ctx.lineWidth   = this.conf.REGION_LINE_WIDTH;
    this._draw_polygon(ctx, pts);
    if ( shape_id === _VIA_RSHAPE.POLYGON ) {
      ctx.closePath();
    }
    ctx.stroke();

    // draw a control box around first point
    ctx.strokeStyle = this.conf.FIRST_VERTEX_BOUNDARY_COLOR;
    ctx.fillStyle = this.conf.FIRST_VERTEX_FILL_COLOR;
    ctx.lineWidth   = this.conf.FIRST_VERTEX_BOUNDARY_WIDTH;
    this._draw_rect(ctx,
                    pts[1] - this.conf.FIRST_VERTEX_CLICK_TOL,
                    pts[2] - this.conf.FIRST_VERTEX_CLICK_TOL,
                    2*this.conf.FIRST_VERTEX_CLICK_TOL,
                    2*this.conf.FIRST_VERTEX_CLICK_TOL);
    ctx.stroke();
    ctx.fill();
  }
}

// note: pts[0] should contain shape-id
_via_file_annotator.prototype._draw_polygon = function(ctx, pts) {
  ctx.beginPath();
  ctx.moveTo(pts[1], pts[2]);
  var n = pts.length;
  for ( var i = 3; i < n; i = i + 2 ) {
    ctx.lineTo(pts[i], pts[i+1]);
  }
}

// control point for resize of region boundaries
_via_file_annotator.prototype._draw_control_point = function(ctx, cx, cy) {
  ctx.beginPath();
  ctx.arc(cx, cy, this.conf.CONTROL_POINT_RADIUS, 0, 2*Math.PI, false);
  ctx.closePath();

  ctx.fillStyle = this.conf.CONTROL_POINT_COLOR;
  ctx.globalAlpha = 1.0;
  ctx.fill();
}


//
// region draw enable/disable
//
_via_file_annotator.prototype._rinput_enable = function() {
  this._state_set(_VIA_RINPUT_STATE.IDLE);
  this.input.style.pointerEvents = 'auto';
  this.input.classList.add('rinput_enabled');
  if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ||
       this.d.store.file[this.fid].type === _VIA_FILE_TYPE.AUDIO
     ) {
    if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
      this.file_html_element.removeAttribute('controls');
    }
    // _via_util_msg_show('At any time, press <span class="key">Space</span> to play or pause the video.');
  }
}

_via_file_annotator.prototype._rinput_disable = function() {
  this._state_set(_VIA_RINPUT_STATE.SUSPEND);
  this.input.style.pointerEvents = 'none';
  this.input.classList.remove('rinput_enabled');
  if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ||
       this.d.store.file[this.fid].type === _VIA_FILE_TYPE.AUDIO
     ) {
    this.file_html_element.setAttribute('controls', 'true');
    //_via_util_msg_show('At any time, press <span class="key">Space</span> to play or pause the video.', true);
  }
}

//
// on-screen file metadata editor
//
_via_file_annotator.prototype._fmetadata_hide = function() {
  this.fmetadata_container.classList.add('hide');
}

_via_file_annotator.prototype._fmetadata_set_position = function() {
  var x = this.left_pad + this.conf.FILE_METADATA_MARGIN;
  var y = this.conf.FILE_METADATA_MARGIN;

  this.fmetadata_container.style.left = Math.round(x) + 'px';
  this.fmetadata_container.style.top  = Math.round(y) + 'px';
}

_via_file_annotator.prototype._fmetadata_toggle = function() {
  this.d.store.config.ui['file_metadata_editor_visible'] = ! this.d.store.config.ui['file_metadata_editor_visible'];
  this._fmetadata_show();
}

_via_file_annotator.prototype._fmetadata_show = function() {
  if ( ! this.d.cache.attribute_group.hasOwnProperty('FILE1_Z0_XY0') ) {
    this.fmetadata_container.innerHTML = '';
    this._fmetadata_hide();
    return;
  }

  var aid_list = this.d.cache.attribute_group['FILE1_Z0_XY0'];
  if ( Object.keys(aid_list).length === 0 ) {
    this.fmetadata_container.innerHTML = '';
    this._fmetadata_hide();
  } else {
    this.fmetadata_container.classList.remove('hide');
    if ( this.d.store.config.ui['file_metadata_editor_visible'] ) {
      var mid_list = this.d.cache.mid_list[this.vid];
      var file_mid = '';
      for ( var mindex in this.d.cache.mid_list[this.vid] ) {
        var mid = this.d.cache.mid_list[this.vid][mindex];
        if ( this.d.store.metadata[mid].z.length === 0 &&
             this.d.store.metadata[mid].xy.length === 0
           ) {
          file_mid = mid;
          break;
        }
      }

      if ( file_mid === '' ) {
        // create a new metadata
        this.d.metadata_add(this.vid, [], [], {}).then( function(ok) {
          this._fmetadata_update(ok.mid);
        }.bind(this), function(err) {
          console.log('failed to show file metadata!');
          console.log(err);
        }.bind(this));
      } else {
        this._fmetadata_update(file_mid);
      }
    } else {
      this.fmetadata_container.innerHTML = '';
      this.fmetadata_container.appendChild(this._fmetadata_toggle_button());
    }

    this._fmetadata_set_position();
  }
}

_via_file_annotator.prototype._fmetadata_toggle_button = function() {
  var span = document.createElement('span');
  span.setAttribute('class', 'text_button');
  if ( this.d.store.config.ui['file_metadata_editor_visible'] ) {
    span.innerHTML = '&larr;';
    span.setAttribute('title', 'Hide (i.e. minimise) file metadata editor');
  } else {
    span.innerHTML = '&rarr;';
    span.setAttribute('title', 'Show file metadata editor (to edit properties of a file like caption, author, etc.)');
  }
  span.addEventListener('click', this._fmetadata_toggle.bind(this));
  return span;
}

_via_file_annotator.prototype._fmetadata_update = function(mid) {
  var aid_list = this.d.cache.attribute_group['FILE1_Z0_XY0'];
  var table = document.createElement('table');
  var header = this._metadata_header_html(aid_list);
  var th = document.createElement('th');
  th.setAttribute('rowspan', '2');
  th.appendChild(this._fmetadata_toggle_button());
  header.appendChild(th)
  table.appendChild(header);

  // show value of each attribute
  var tbody = document.createElement('tbody');
  var tr = document.createElement('tr');
  tr.setAttribute('data-mid', mid);

  var aid;
  for ( var aindex in aid_list ) {
    aid = aid_list[aindex];
    var td = document.createElement('td');
    td.setAttribute('data-aid', aid);
    td.appendChild( this._metadata_attribute_io_html_element(mid, aid) );
    tr.appendChild(td);
  }
  var td = document.createElement('td');
  tr.appendChild(td); // empty row for control buttons

  tbody.appendChild(tr);
  table.appendChild(tbody);

  this.fmetadata_container.innerHTML = '';
  this.fmetadata_container.appendChild(table);
}

//
// on-screen spatial metadata editor
//
_via_file_annotator.prototype._smetadata_hide = function() {
  this.smetadata_container.classList.add('hide');
}

_via_file_annotator.prototype._smetadata_set_position = function() {
  var mid = this.selected_mid_list[0];
  var x = this.left_pad + this.creg[mid][1];
  var y = this.conf.REGION_SMETADATA_MARGIN + this.creg[mid][2];
  var shape_id = this.creg[mid][0];
  switch(shape_id) {
  case _VIA_RSHAPE.CIRCLE:
    y = y + this.creg[mid][3];
    break;
  case _VIA_RSHAPE.RECTANGLE:
  case _VIA_RSHAPE.ELLIPSE:
    y = y + this.creg[mid][4];
    break;
  case _VIA_RSHAPE.POLYGON:
  case _VIA_RSHAPE.POLYLINE:
  case _VIA_RSHAPE.EXTREME_RECTANGLE:
  case _VIA_RSHAPE.EXTREME_CIRCLE:
  case _VIA_RSHAPE.LINE:
    var ymax_x = this.creg[mid][1];
    var ymax = this.creg[mid][2];
    var n = this.creg[mid].length;
    for ( var i = 4; i < n; i = i + 2 ) {
      if ( this.creg[mid][i] > ymax ) {
        ymax = this.creg[mid][i];
        ymax_x = this.creg[mid][i-1];
      }
    }
    y = ymax + this.conf.REGION_SMETADATA_MARGIN;
    x = ymax_x + this.conf.REGION_SMETADATA_MARGIN;
    break;
  }
  this.smetadata_container.style.left = Math.round(x) + 'px';
  this.smetadata_container.style.top  = Math.round(y) + 'px';
}

_via_file_annotator.prototype._smetadata_toggle = function() {
  this.d.store.config.ui['spatial_metadata_editor_visible'] = ! this.d.store.config.ui['spatial_metadata_editor_visible'];
  this._smetadata_show();
}

_via_file_annotator.prototype._smetadata_toggle_button = function() {
  var span = document.createElement('span');
  span.setAttribute('class', 'text_button');
  if ( this.d.store.config.ui['spatial_metadata_editor_visible'] ) {
    span.innerHTML = '&larr;';
    span.setAttribute('title', 'Hide (i.e. minimise) spatial metadata editor');
  } else {
    span.innerHTML = '&rarr;';
    span.setAttribute('title', 'Show spatial metadata editor (to edit properties of a spatial region)');
  }
  span.addEventListener('click', this._smetadata_toggle.bind(this));
  return span;
}

_via_file_annotator.prototype._smetadata_show = function() {
  if ( this.selected_mid_list.length === 1 ) {
    this.smetadata_container.classList.remove('hide');
    this._smetadata_update();
    this._smetadata_set_position();
  } else {
    this._smetadata_hide();
  }
}

_via_file_annotator.prototype._smetadata_update = function() {
  var aid_list = this.d._cache_get_attribute_group(['FILE1_Z1_XY1',
                                                    'FILE1_Z0_XY1',
                                                    'FILE1_Z2_XY0']);
  if ( Object.keys(aid_list).length === 0 ) {
    // no attributes to display
    this.smetadata_container.innerHTML = '';
    this._smetadata_hide();
    return;
  }

  if ( this.d.store.config.ui['spatial_metadata_editor_visible'] ) {
    var table = document.createElement('table');
    var header = this._metadata_header_html(aid_list);
    var th = document.createElement('th');
    th.setAttribute('rowspan', '2');
    th.appendChild(this._smetadata_toggle_button());
    header.appendChild(th)
    table.appendChild(header);

    // show value of each attribute
    var tbody = document.createElement('tbody');
    var tr = document.createElement('tr');
    tr.setAttribute('data-mid', mid);

    var mid = this.selected_mid_list[0];
    var aid;
    for ( var aindex in aid_list ) {
      aid = aid_list[aindex];
      var td = document.createElement('td');
      td.setAttribute('data-aid', aid);
      td.appendChild( this._metadata_attribute_io_html_element(mid, aid) );
      tr.appendChild(td);
    }
    var td = document.createElement('td');
    tr.appendChild(td); // empty row for control buttons

    tbody.appendChild(tr);
    table.appendChild(tbody);

    this.smetadata_container.innerHTML = '';
    this.smetadata_container.appendChild(table);
  } else {
    this.smetadata_container.innerHTML = '';
    this.smetadata_container.appendChild(this._smetadata_toggle_button());
  }
}

_via_file_annotator.prototype._metadata_header_html = function(aid_list) {
  var tr = document.createElement('tr');
  var aid;
  for ( var aindex in aid_list ) {
    aid = aid_list[aindex];
    var th = document.createElement('th');
    th.innerHTML = this.d.store.attribute[aid].aname;
    tr.appendChild(th);
  }
  return tr;
}

_via_file_annotator.prototype._metadata_on_change = function(e) {
  var mid = e.target.dataset.mid;
  var aid = e.target.dataset.aid;
  var aval = e.target.value;
  if ( e.target.type === 'checkbox' &&
       this.d.store.metadata[mid].av.hasOwnProperty(aid)
     ) {
    var values = this.d.store.metadata[mid].av[aid].split(',');
    if ( this.d.store.metadata[mid].av[aid] !== '' ) {
      var vindex = values.indexOf(e.target.value);
      if ( e.target.checked ) {
        // add this value
        if ( vindex === -1 ) {
          values.push(e.target.value);
        }
      } else {
        // remove this value
        var vindex = values.indexOf(aval);
        if ( vindex !== -1 ) {
          values.splice(vindex, 1);
        }
      }
      aval = values.join(',');
    }
  }

  this.d.metadata_update_av(this.vid, mid, aid, aval).then( function(ok) {
    //console.log( JSON.stringify(this.d.store.metadata[ok.mid].av) );
  }.bind(this));
}

_via_file_annotator.prototype._metadata_attribute_io_html_element = function(mid, aid) {
  var aval  = this.d.store.metadata[mid].av[aid];
  var dval  = this.d.store.attribute[aid].default_option_id;
  var atype = this.d.store.attribute[aid].type;
  var el;

  switch(atype) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }
    el = document.createElement('textarea');
    el.addEventListener('change', this._metadata_on_change.bind(this));
    el.innerHTML = aval;
    break;

  case _VIA_ATTRIBUTE_TYPE.SELECT:
    el = document.createElement('select');
    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }

    var option_selected = false;
    for ( var oid in this.d.store.attribute[aid].options ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', oid);
      oi.innerHTML = this.d.store.attribute[aid].options[oid];
      if ( oid === aval ) {
        oi.setAttribute('selected', 'true');
        option_selected = true;
      }
      el.appendChild(oi);
    }
    if(!option_selected) {
      el.selectedIndex = -1; // to indicate that nothing has been selected
    }
    el.addEventListener('change', this._metadata_on_change.bind(this));
    break;

  case _VIA_ATTRIBUTE_TYPE.RADIO:
    el = document.createElement('div');

    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }

    for ( var oid in this.d.store.attribute[aid].options ) {
      var radio = document.createElement('input');
      radio.setAttribute('type', 'radio');
      radio.setAttribute('value', oid);
      radio.setAttribute('data-mid', mid);
      radio.setAttribute('data-aid', aid);
      radio.setAttribute('name', this.d.store.attribute[aid].aname);
      if ( oid === aval ) {
        radio.setAttribute('checked', true);
      }
      radio.addEventListener('change', this._metadata_on_change.bind(this));
      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(radio);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.CHECKBOX:
    el = document.createElement('div');
    if ( typeof(aval) === 'undefined' ) {
      if ( typeof(dval) === 'undefined' ) {
        aval = '';
      } else {
        aval = dval;
      }
    }
    var values = aval.split(',');
    for ( var oid in this.d.store.attribute[aid].options ) {
      var checkbox = document.createElement('input');
      checkbox.setAttribute('type', 'checkbox');
      checkbox.setAttribute('value', oid);
      checkbox.setAttribute('data-mid', mid);
      checkbox.setAttribute('data-aid', aid);
      checkbox.setAttribute('name', this.d.store.attribute[aid].aname);

      if ( values.indexOf(oid) !== -1 ) {
        checkbox.setAttribute('checked', true);
      }
      checkbox.addEventListener('change', this._metadata_on_change.bind(this));
      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(checkbox);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  default:
    console.log('attribute type ' + atype + ' not implemented yet!');
    var el = document.createElement('span');
    el.innerHTML = aval;
  }
  el.setAttribute('data-mid', mid);
  el.setAttribute('data-aid', aid);
  return el;
}

//
// zoom
//
_via_file_annotator.prototype._zoom_in = function() {
  if(this.va.zoom_mode === _VIA_ZOOM_MODE.FITHEIGHT ||
     this.va.zoom_mode === _VIA_ZOOM_MODE.FITWIDTH) {
    this.va.zoom_mode = _VIA_ZOOM_MODE.SCALE;
    this.va.zoom_scale_value_index = _VIA_ZOOM_SCALE_DEFAULT_INDEX;
    _via_util_msg_show('Showing original size');
  } else {
    var next_zoom_scale_index = this.va.zoom_scale_value_index + 1;
    if(next_zoom_scale_index === _VIA_ZOOM_SCALE_VALUE_LIST.length) {
      _via_util_msg_show('Cannot zoom in any further');
      return;
    }
    this.va.zoom_scale_value_index = next_zoom_scale_index;
    var zoom_scale_value = _VIA_ZOOM_SCALE_VALUE_LIST[this.va.zoom_scale_value_index];
    _via_util_msg_show('Showing at zoom level of ' + zoom_scale_value + 'X');
  }
  this.va.view_show(this.vid);
}

_via_file_annotator.prototype._zoom_out = function() {
  if(this.va.zoom_mode === _VIA_ZOOM_MODE.FITHEIGHT ||
     this.va.zoom_mode === _VIA_ZOOM_MODE.FITWIDTH) {
    this.va.zoom_mode = _VIA_ZOOM_MODE.SCALE;
    this.zoom_scale_value_index = _VIA_ZOOM_SCALE_DEFAULT_INDEX;
  } else {
    if(this.va.zoom_scale_value_index === 0) {
      _via_util_msg_show('Cannot zoom out any further');
      return;
    }
    this.va.zoom_scale_value_index = this.va.zoom_scale_value_index - 1;
    var zoom_scale_value = _VIA_ZOOM_SCALE_VALUE_LIST[this.va.zoom_scale_value_index];
    _via_util_msg_show('Showing at zoom level of ' + zoom_scale_value + 'X');
  }
  this.va.view_show(this.vid);
}


_via_file_annotator.prototype._zoom_fit_screen = function() {
  switch(this.va.zoom_mode) {
  case _VIA_ZOOM_MODE.FITHEIGHT:
    this.va.zoom_mode = _VIA_ZOOM_MODE.FITWIDTH;
    _via_util_msg_show('Scaled image to fill screen width');
    break;
  case _VIA_ZOOM_MODE.FITWIDTH:
    this.va.zoom_mode = _VIA_ZOOM_MODE.FITHEIGHT;
    _via_util_msg_show('Scaled image to fill screen height');
    break;
  default:
    this.va.zoom_mode = _VIA_ZOOM_MODE.FITHEIGHT;
  }
  this.va.view_show(this.vid);
}
</script>
<!-- END: Contents of file: js/_via_file_annotator.js-->
<!-- START: Contents of file: js/_via_temporal_segmenter.js-->
<script>
/**
 * @class
 * @classdesc Marks time segments of a {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement|HTMLMediaElement}
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @since 5 Mar. 2019
 * @fires _via_temporal_segmenter#segment_add
 * @fires _via_temporal_segmenter#segment_del
 * @fires _via_temporal_segmenter#segment_edit
 * @param {Element} container HTML container element like <div>
 * @param {HTMLMediaElement} media_element HTML video of audio
 */

'use strict';

function _via_temporal_segmenter(file_annotator, container, vid, data, media_element) {
  this._ID = '_via_temporal_segmenter_';
  this.fa = file_annotator;
  this.c = container;
  this.vid = vid;
  this.d = data;
  this.m = media_element;

  this.groupby = false;
  this.groupby_aid = '';
  this.group = {};
  this.gid_list = [];
  this.selected_gindex = -1;
  this.selected_mindex = -1;
  this.edge_show_time = -1;

  // spatial mid
  this.smid = {};

  this.DRAW_LINE_WIDTH = 2;
  this.EDGE_UPDATE_TIME_DELTA = 1/50;  // in sec
  this.TEMPORAL_SEG_MOVE_OFFSET = 1;   // in sec
  this.DEFAULT_TEMPORAL_SEG_LEN = 1;   // in sec
  this.GTIMELINE_HEIGHT = 8;           // units of char width
  this.GTIMELINE_ZOOM_OFFSET = 4;      // in pixels
  this.DEFAULT_WIDTH_PER_SEC = 6;      // units of char width
  this.GID_COL_WIDTH = 15;             // units of char width
  this.METADATA_CONTAINER_HEIGHT = 22; // units of char width
  this.METADATA_EDGE_TOL = 0.1;
  this.GTIMELINE_REGION_MARKER_MOUSE_TOL = 0.1; // in sec.

  this.PLAYBACK_MODE = { NORMAL:'1', REVIEW_SEGMENT:'2', REVIEW_GAP:'3' };
  this.current_playback_mode = this.PLAYBACK_MODE.NORMAL;

  this.metadata_move_is_ongoing = false;
  this.metadata_resize_is_ongoing = false;
  this.metadata_resize_edge_index = -1;
  this.metadata_ongoing_update_x = [0, 0];
  this.metadata_move_start_x = 0;
  this.metadata_move_dx = 0;
  this.metadata_last_added_mid = '';
  this.is_init_success = false;

  //this.show_audio = true;             // experimental, hence disabled by default
  this.audio_analyser_active = false;

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );
  this.d.on_event('attribute_update', this._ID, this._on_event_attribute_update.bind(this));
  this.d.on_event('metadata_update_bulk', this._ID, this._on_event_metadata_update_bulk.bind(this));
  // metadata_add and metadata_update events are captured by
  // _via_view_annotator.prototype._on_event_metadata_add() and
  // _via_view_annotator.prototype._on_event_metadata_update()
  // and passed on to the child temporal_segmenter

  if ( ! this.m instanceof HTMLMediaElement ) {
    throw 'media element must be an instance of HTMLMediaElement!';
  }

  // colour
  this.COLOR_LIST = ["#E69F00", "#56B4E9", "#009E73", "#0072B2", "#D55E00", "#CC79A7", "#F0E442"];
  this.NCOLOR = this.COLOR_LIST.length;

  this.current_playback_mode = this.PLAYBACK_MODE.NORMAL;

  this._init();
}

_via_temporal_segmenter.prototype._init = function() {
  if ( ! this.d.store.config.ui.hasOwnProperty('temporal_segment_metadata_editor_visible') ) {
    this.d.store.config.ui['temporal_segment_metadata_editor_visible'] = true;
  }

  this.group_aid_candidate_list = [];
  if ( this.d.cache.attribute_group.hasOwnProperty('FILE1_Z2_XY0') ) {
    for ( var aindex in this.d.cache.attribute_group['FILE1_Z2_XY0'] ) {
      this.group_aid_candidate_list.push( this.d.cache.attribute_group['FILE1_Z2_XY0'][aindex] );
    }
  }
  if ( this.d.cache.attribute_group.hasOwnProperty('FILE1_ZN_XYN') ) {
    for ( var aindex in this.d.cache.attribute_group['FILE1_ZN_XYN'] ) {
      this.group_aid_candidate_list.push( this.d.cache.attribute_group['FILE1_ZN_XYN'][aindex] );
    }
  }

  if ( this.group_aid_candidate_list.length ) {
    this._init_on_success(this.group_aid_candidate_list[0]);
    this.is_init_success = true;
  } else {
    // add a default temporal segment attribute
    var promise = this.d.attribute_add('TEMPORAL-SEGMENTS',
                                       'FILE1_Z2_XY0',
                                       _VIA_ATTRIBUTE_TYPE.SELECT,
                                       'Temporal segment attribute added by default',
                                       {'default':'Default'},
                                       '');
    promise.then(function(new_attribute_aid) {
      this.group_aid_candidate_list.push(new_attribute_aid);
      this._init_on_success(new_attribute_aid);
      this.is_init_success = true;
    }.bind(this), function(err) {
      this._init_on_fail();
      this.is_init_success = false;
    }.bind(this));
  }
}

_via_temporal_segmenter.prototype._init_on_fail = function() {
  this.c.innerHTML = '<p>You must define an attribute with anchor "Temporal Segment in Video or Audio" in order to define temporal segments in this file. Click&nbsp;<svg class="svg_icon" viewbox="0 0 24 24"><use xlink:href="#micon_insertcomment"></use></svg>&nbsp;button to define such attributes using attribute editor.</p><p>After defining the attribute, <span class="text_button" onclick="via.va.view_show(via.va.vid);">reload this file</span>.' ;
}

_via_temporal_segmenter.prototype._init_on_success = function(groupby_aid) {
  try {
    this.fid = this.d.store.view[this.vid].fid_list[0];
    this.file = this.d.store.file[this.fid];

    this.c.innerHTML = '';

    this._group_init(groupby_aid);
    if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
      this._thumbview_init();
    }

    this._vtimeline_init();
    this._tmetadata_init();
    this._toolbar_init();

    // trigger the update of animation frames
    this._redraw_all();
    this._redraw_timeline();
  } catch(err) {
    console.log(err);
  }
}

//
// All animation frame routines
//
_via_temporal_segmenter.prototype._redraw_all = function() {
  window.requestAnimationFrame(this._redraw_all.bind(this));
  var tnow = this.m.currentTime;
  this._update_playback_rate(tnow);

  if ( tnow < this.tmetadata_gtimeline_tstart ||
       tnow > this.tmetadata_gtimeline_tend
     ) {
    if ( ! this.m.paused ) {
      var new_tstart = Math.floor(tnow);
      this._tmetadata_boundary_update(new_tstart)
      this._tmetadata_gtimeline_draw();
    } else {
      //this.m.currentTime = this.tmetadata_gtimeline_tstart;
    }
  }

  // lock playback in the selected temporal segment
  if ( this.selected_mindex !== -1 ) {
    if ( ! this.m.paused ) {
      var t = this.d.store.metadata[this.selected_mid].z;
      if ( tnow > t[1] ) {
        this.m.currentTime = t[0];
      }
      if ( tnow < t[0] ) {
        this.m.currentTime = t[0];
      }
    }
  }

  //this._redraw_timeline();

  // draw marker to show current time in group timeline and group metadata
  this._tmetadata_draw_currenttime_mark(tnow);

  if(this.show_audio) {
    this._tmetadata_gtimeline_draw_audio();
  }
}

_via_temporal_segmenter.prototype._update_playback_rate = function(t) {
  //console.log(this.current_playback_mode + ':' + t)
  if ( this.current_playback_mode !== this.PLAYBACK_MODE.NORMAL ) {
    var mindex = this._tmetadata_group_gid_metadata_at_time(t);
    if ( mindex !== -1 ) {
      if ( this.current_playback_mode === this.PLAYBACK_MODE.REVIEW_SEGMENT ) {
        this._toolbar_playback_rate_set(1);
      } else {
        this._toolbar_playback_rate_set(10);
      }
    } else {
      if ( this.current_playback_mode === this.PLAYBACK_MODE.REVIEW_GAP ) {
        this._toolbar_playback_rate_set(1);
      } else {
        this._toolbar_playback_rate_set(10);
      }
    }
  } else {
    //this._toolbar_playback_rate_set(1);
  }
}

_via_temporal_segmenter.prototype._redraw_timeline = function() {
  // draw the full video timeline (on the top)
  this._vtimeline_mark_draw();
  // draw group timeline
  this._tmetadata_gtimeline_draw();
}

//
// thumbnail viewer
//
_via_temporal_segmenter.prototype._thumbview_init = function() {
  if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
    this.thumbnail_container = document.createElement('div');
    this.thumbnail_container.setAttribute('class', 'thumbnail_container');
    this.thumbnail_container.setAttribute('style', 'display:none; position:absolute; top:0; left:0;');
    this.c.appendChild(this.thumbnail_container);

    // initialise thumbnail viewer
    this.thumbnail = new _via_video_thumbnail(this.fid, this.d);
    this.thumbnail.load();
  }
}

_via_temporal_segmenter.prototype._thumbview_show = function(time, x, y) {
  if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
    this.thumbnail_container.innerHTML = '';
    this.thumbnail_container.appendChild(this.thumbnail.get_thumbnail(time));
    this.thumbnail_container.style.display = 'inline-block';

    this.thumbnail_container.style.left = x + this.linehn[2] + 'px';
    this.thumbnail_container.style.top  = y + this.linehn[4] + 'px';
  }
}

_via_temporal_segmenter.prototype._thumbview_hide = function(t) {
  if ( this.d.store.file[this.fid].type === _VIA_FILE_TYPE.VIDEO ) {
    this.thumbnail_container.style.display = 'none';
  }
}

//
// Full video timeline
//
_via_temporal_segmenter.prototype._vtimeline_init = function() {
  this.vtimeline = document.createElement('canvas');
  this.vtimeline.setAttribute('class', 'video_timeline');
  this.vtimeline.style.cursor = 'pointer';
  this.vtimeline.addEventListener('mousedown', this._vtimeline_on_mousedown.bind(this));
  this.vtimeline.addEventListener('mousemove', this._vtimeline_on_mousemove.bind(this));
  this.vtimeline.addEventListener('mouseout', this._vtimeline_on_mouseout.bind(this));

  var ctx = this.vtimeline.getContext('2d', {alpha:false});
  ctx.font = '10px Sans';
  this.char_width = ctx.measureText('M').width;
  this.vtimeline.width = this.c.clientWidth;
  this.vtimeline.height = Math.floor(2*this.char_width);
  this.padx = this.char_width;
  this.pady = this.char_width;
  this.lineh = Math.floor(this.char_width);
  this.linehn = []; // contains multiples of line_height for future ref.
  for ( var i = 0; i < 20; ++i ) {
    this.linehn[i] = i * this.lineh;
  }
  this.linehb2 = Math.floor(this.char_width/2);
  this.vtimelinew = Math.floor(this.vtimeline.width - 2*this.padx);

  // clear
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, this.vtimeline.width, this.vtimeline.height);

  // draw line
  ctx.strokeStyle = '#999999';
  ctx.fillStyle = '#999999';
  ctx.lineWidth = this.DRAW_LINE_WIDTH;
  ctx.beginPath();
  ctx.moveTo(this.padx, 1);
  ctx.lineTo(this.padx + this.vtimelinew, 1);
  ctx.stroke();

  // draw time gratings and corresponding label
  var start = this.padx;
  var time = 0;
  var width_per_tick = 10 * this.char_width;
  var end = this.vtimelinew - width_per_tick;
  var width_per_sec  = this._vtimeline_time2canvas(1);

  ctx.beginPath();
  while ( start <end && time < this.m.duration ) {
    ctx.moveTo(start, 1);
    ctx.lineTo(start, this.lineh - 1);

    time = this._vtimeline_canvas2time(start);
    ctx.fillText(this._time2strms(time), start, this.linehn[2] - 1);

    start = start + 10*this.char_width;
  }
  ctx.stroke();

  // draw the end mark
  var endx = this._vtimeline_time2canvas(this.m.duration);
  ctx.beginPath();
  ctx.moveTo(endx, 1);
  ctx.lineTo(endx, this.lineh - 1);
  ctx.stroke();
  var tendstr = this._time2strms(this.m.duration);
  var tendstr_width = ctx.measureText(tendstr).width;
  ctx.fillStyle = '#999999';
  ctx.fillText(tendstr, endx - tendstr_width, this.linehn[2] - 1);

  //// timeline mark showing the current video time
  //// and placed just above the full video timeline
  this.vtimeline_mark = document.createElement('canvas');
  this.vtimeline_mark.setAttribute('class', 'video_timeline_mark');
  this.vtimeline_mark_ctx = this.vtimeline_mark.getContext('2d', { alpha:false });
  this.vtimeline_mark.width = this.vtimeline.width;
  this.vtimeline_mark.height = this.linehn[2];

  this.c.appendChild(this.vtimeline_mark);
  this.c.appendChild(this.vtimeline);
}

_via_temporal_segmenter.prototype._vtimeline_time2canvas = function(t) {
  return Math.floor( ( ( this.vtimelinew * t ) / this.m.duration ) + this.padx );
}

_via_temporal_segmenter.prototype._vtimeline_canvas2time = function(x) {
  var t = ( ( x - this.padx ) / this.vtimelinew ) * this.m.duration;
  return Math.max(0, Math.min(t, this.m.duration) );
}

_via_temporal_segmenter.prototype._vtimeline_mark_draw = function() {
  // continually update the video timeline timer (required for responsive update)
  window.requestAnimationFrame(this._vtimeline_mark_draw.bind(this));

  var time = this.m.currentTime;
  var cx = this._vtimeline_time2canvas(time);

  // clear
  this.vtimeline_mark_ctx.font = '16px Mono';
  this.vtimeline_mark_ctx.fillStyle = 'white';
  this.vtimeline_mark_ctx.fillRect(0, 0,
                                   this.vtimeline_mark.width,
                                   this.vtimeline_mark.height);

  // draw arrow
  this.vtimeline_mark_ctx.fillStyle = 'black';
  this.vtimeline_mark_ctx.beginPath();
  this.vtimeline_mark_ctx.moveTo(cx, this.linehn[2]);
  this.vtimeline_mark_ctx.lineTo(cx - this.linehb2, this.lineh);
  this.vtimeline_mark_ctx.lineTo(cx + this.linehb2, this.lineh);
  this.vtimeline_mark_ctx.moveTo(cx, this.linehn[2]);
  this.vtimeline_mark_ctx.fill();

  // draw current time
  this.vtimeline_mark_ctx.fillStyle = '#666666';
  var tstr = this._time2strms(time);
  var twidth = this.vtimeline_mark_ctx.measureText(tstr).width;
  var tx = cx + this.lineh;
  if ( cx + twidth > this.vtimelinew ) {
    tx = tx - twidth - this.linehn[2];
  }
  this.vtimeline_mark_ctx.fillText(tstr, tx, this.linehn[2] - 2);
}

_via_temporal_segmenter.prototype._vtimeline_on_mousedown = function(e) {
  var canvas_x = e.offsetX;
  var time = this._vtimeline_canvas2time(canvas_x);
  this.m.currentTime = time;

  var new_tstart = Math.floor(time);
  this._tmetadata_boundary_update(new_tstart)
  this._tmetadata_gtimeline_draw();
}

_via_temporal_segmenter.prototype._vtimeline_on_mousemove = function(e) {
  var time = this._vtimeline_canvas2time(e.offsetX);
  this._thumbview_show(time, e.offsetX, e.offsetY);
}

_via_temporal_segmenter.prototype._vtimeline_on_mouseout = function(e) {
  this._thumbview_hide();
}

//
// Metadata Panel
//
_via_temporal_segmenter.prototype._tmetadata_init = function() {
  this.tmetadata_container = document.createElement('div');
  this.tmetadata_container.setAttribute('class', 'tmetadata_container');

  // group timeline container
  var gheader_container = document.createElement('div');
  gheader_container.setAttribute('class', 'gheader_container');
  var gheader_grid = document.createElement('div');
  gheader_grid.setAttribute('class', 'twocolgrid');
  var group_aname_container = document.createElement('div');
  group_aname_container.setAttribute('class', 'gidcol');

  // group variable selector
  if ( this.group_aid_candidate_list.length ) {
    this.group_aname_select = document.createElement('select');
    this.group_aname_select.setAttribute('title', 'Select the variable name that should be used for temporal segmentation. These variables can be edited using the the attribute editor.');
    this.group_aname_select.addEventListener('change', this._tmetadata_onchange_groupby_aid.bind(this));

    for ( var aindex in this.group_aid_candidate_list ) {
      var aid = this.group_aid_candidate_list[aindex];
      var oi = document.createElement('option');
      oi.innerHTML = this.d.store.attribute[aid].aname;
      oi.setAttribute('value', aid);
      if ( aid === this.groupby_aid ) {
        oi.setAttribute('selected', '');
      }
      this.group_aname_select.appendChild(oi);
    }
    group_aname_container.appendChild(this.group_aname_select);
  }

  this.gtimeline_container = document.createElement('div');
  this.gtimeline_container.setAttribute('class', 'gtimeline_container');
  gheader_grid.appendChild(group_aname_container);
  gheader_grid.appendChild(this.gtimeline_container);

  gheader_container.appendChild(gheader_grid);
  this.tmetadata_container.appendChild(gheader_container);
  this.c.appendChild(this.tmetadata_container);

  // initialise the gtimeline (timeline for temporal segmentation, requires width of container)
  this.timeline_container_width = this.gtimeline_container.clientWidth - Math.floor(2 * this.padx);
  this.tmetadata_timelinew = this.timeline_container_width - Math.floor(2 * this.padx);
  this.tmetadata_width_per_sec = this.linehn[this.DEFAULT_WIDTH_PER_SEC];
  this._tmetadata_boundary_update(0); // determine the boundary of gtimeline

  this._tmetadata_gtimeline_init();
  this.gtimeline_container.appendChild(this.gtimeline);

  this.gmetadata_container = document.createElement('div');
  this.gmetadata_container.setAttribute('class', 'gmetadata_container');

  var gtimeline_container_maxheight = this.fa.va.gtimeline_container_height - this.METADATA_CONTAINER_HEIGHT + 5; // 5 is offset obtained using visual inspection
  this.gmetadata_container.setAttribute('style', 'max-height:' + gtimeline_container_maxheight + 'ch;');

  this.gmetadata_grid = document.createElement('div');
  this.gmetadata_grid.setAttribute('class', 'twocolgrid');
  this._tmetadata_gmetadata_update();
  this.gmetadata_container.appendChild(this.gmetadata_grid);
  this.tmetadata_container.appendChild(this.gmetadata_container);
  // Note: this.tmetadata_container has already been added to parent container

  this.tseg_metadata_container = document.createElement('div');
  this.tseg_metadata_container.setAttribute('class', 'tseg_metadata_container');
  this._tseg_metadata_hide();
  this.tmetadata_container.appendChild(this.tseg_metadata_container);

  if ( this.gid_list.length ) {
    this._tmetadata_group_gid_sel(0);
  }
}

_via_temporal_segmenter.prototype._tmetadata_audio_init = function() {
  try {
    this.audioctx = new (window.AudioContext || window.webkitAudioContext)();
    this.analyser = this.audioctx.createAnalyser();
    this.audiosrc = this.audioctx.createMediaElementSource(this.m);
    this.audiosrc.connect(this.analyser);
    this.analyser.connect(this.audioctx.destination)

    this.analyser.fftSize = 32;
    this.audio_data = new Float32Array(this.analyser.frequencyBinCount);
    this.audio_analyser_active = true;
  } catch(ex) {
    this.audio_analyser_active = false;
  }
}

_via_temporal_segmenter.prototype._tmetadata_gmetadata_update = function() {
  this.gmetadata_grid.innerHTML = '';

  // initialise the gmetadata (temporal segment corresponding to each group)
  this.gcanvas = {}; // contains a list of canvas for each group
  this.gctx = {};    // contains the corresponding drawing context
  var gindex, gid;
  for ( gindex in this.gid_list ) {
    gid = this.gid_list[gindex];

    // column containing value of timeline-id
    var gid_container = document.createElement('div');
    gid_container.setAttribute('class', 'gidcol');
    var gid_input = document.createElement('input');
    gid_input.setAttribute('type', 'text');
    if ( this.d.store.attribute[this.groupby_aid].type === _VIA_ATTRIBUTE_TYPE.SELECT &&
         this.d.store.attribute[this.groupby_aid].options.hasOwnProperty(gid)
       ) {
      gid_input.setAttribute('value', this.d.store.attribute[this.groupby_aid].options[gid]);
    } else {
      gid_input.setAttribute('value', gid);
    }

    gid_input.setAttribute('title', this.d.store.attribute[this.groupby_aid].aname +
                           ' = ' + gid);
    gid_input.setAttribute('data-gid', gid);
    gid_input.addEventListener('change', this._tmetadata_group_update_gid.bind(this));
    gid_container.appendChild(gid_input);

    // column containing temporal segments for this value timeline-id
    this._tmetadata_group_gid_init(gid);
    var gid_metadata_container = document.createElement('div');
    gid_metadata_container.appendChild(this.gcanvas[gid]);

    this.gmetadata_grid.appendChild(gid_container);
    this.gmetadata_grid.appendChild(gid_metadata_container);
  }
}

_via_temporal_segmenter.prototype._tmetadata_gid_list_reorder = function(gindex_now, gindex_new) {
  var new_loc_gid = this.gid_list[gindex_new];
  this.gid_list[gindex_new] = this.gid_list[gindex_now];
  this.gid_list[gindex_now] = new_loc_gid;
  this._tmetadata_gmetadata_update();
}


_via_temporal_segmenter.prototype._tmetadata_onchange_groupby_aid = function(e) {
  this.group_aname_select.blur(); // remove selection of dropdown
  this._tseg_metadata_hide();
  var new_groupby_aid = e.target.options[e.target.selectedIndex].value;
  this._group_init( new_groupby_aid );
  this._tmetadata_gmetadata_update();
  this._tmetadata_boundary_update(this.tmetadata_gtimeline_tstart)
  this._tmetadata_group_gid_sel(0);
  this._tmetadata_gtimeline_draw();
}

_via_temporal_segmenter.prototype._tmetadata_boundary_move = function(dt) {
  var new_start = Math.floor(this.tmetadata_gtimeline_tstart + dt);
  if ( new_start >= 0 &&
       new_start < this.m.duration
     ) {
    this._tmetadata_boundary_update(new_start);
    if ( this.m.currentTime < this.tmetadata_gtimeline_tstart ) {
      this.m.currentTime = this.tmetadata_gtimeline_tstart;
    } else {
      if ( this.m.currentTime > this.tmetadata_gtimeline_tend ) {
        this.m.currentTime = this.tmetadata_gtimeline_tend;
      }
    }
  } else {
    _via_util_msg_show('Cannot move beyond the video timeline boundary!');
  }
}

_via_temporal_segmenter.prototype._tmetadata_boundary_update = function(tstart) {
  this.tmetadata_gtimeline_tstart = tstart;
  this.tmetadata_gtimeline_xstart = this.padx;

  var t = this.tmetadata_gtimeline_tstart;
  var tx = this.tmetadata_gtimeline_xstart;
  var endx = tx + this.tmetadata_timelinew;
  this.tmetadata_gtimeline_mark_x = [];
  this.tmetadata_gtimeline_mark_time_str = [];
  while ( tx <= endx && t <= this.m.duration ) {
    this.tmetadata_gtimeline_mark_x.push(tx);
    this.tmetadata_gtimeline_mark_time_str.push( this._time2str(t) );

    t = t + 1;
    tx = tx + this.tmetadata_width_per_sec;
  }
  if ( t >= this.m.duration ) {
    this.tmetadata_gtimeline_tend = this.m.duration;
  } else {
    this.tmetadata_gtimeline_tend = t - 1;
  }
  this.tmetadata_gtimeline_xend = this.tmetadata_gtimeline_xstart + (this.tmetadata_gtimeline_tend - this.tmetadata_gtimeline_tstart) * this.tmetadata_width_per_sec;

  // asynchronously pull out the metadata in the current group timeline boundary
  this.tmetadata_gtimeline_mid = {};
  setTimeout( this._tmetadata_boundary_fetch_all_mid.bind(this), 0);
}

_via_temporal_segmenter.prototype._tmetadata_boundary_fetch_all_mid = function() {
  var gindex;
  var t0, t1;

  // gather all temporal mid
  for ( gindex in this.gid_list ) {
    this._tmetadata_boundary_fetch_gid_mid( this.gid_list[gindex] );
    this._tmetadata_group_gid_draw( this.gid_list[gindex] );
  }

  // gather all spatial mid
  this._tmetadata_boundary_fetch_spatial_mid();
}

_via_temporal_segmenter.prototype._tmetadata_boundary_fetch_spatial_mid = function() {
  var mid, avalue, time;
  this.smid = {};
  for ( var mindex in this.d.cache.mid_list[this.vid] ) {
    mid = this.d.cache.mid_list[this.vid][mindex];
    if ( this.d.store.metadata[mid].z.length === 1 &&
         this.d.store.metadata[mid].xy.length !== 0
       ) {
      time = this.d.store.metadata[mid].z[0].toString();
      if ( time >= this.tmetadata_gtimeline_tstart &&
           time <= this.tmetadata_gtimeline_tend ) {
        if ( ! this.smid.hasOwnProperty(time) ) {
          this.smid[time] = [];
        }
        this.smid[time].push(mid);
      }
    }
  }
  this._redraw_timeline();
}

_via_temporal_segmenter.prototype._tmetadata_boundary_add_spatial_mid = function(mid) {
  if ( this.d.store.metadata[mid].z.length === 1 &&
       this.d.store.metadata[mid].xy.length !== 0
     ) {
    var time = this.d.store.metadata[mid].z[0].toString();
    if ( time >= this.tmetadata_gtimeline_tstart &&
         time <= this.tmetadata_gtimeline_tend ) {
      if ( ! this.smid.hasOwnProperty(time) ) {
        this.smid[time] = [];
      }
      this.smid[time].push(mid);
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_boundary_del_spatial_mid = function(mid) {
  for ( var time in this.smid ) {
    if ( this.smid[time].length ) {
      var mindex = this.smid[time].indexOf(mid);
      if ( mindex !== -1 ) {
        this.smid[time].splice(mindex, 1);
        return;
      }
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_boundary_fetch_gid_mid = function(gid) {
  this.tmetadata_gtimeline_mid[gid] = [];
  var mid_list = [];
  var mindex, mid, t0, t1;
  for ( mindex in this.group[gid] ) {
    mid = this.group[gid][mindex];
    if ( this.d.store.metadata[mid].z.length === 2 &&
         this.d.store.metadata[mid].xy.length === 0
       ) {
      t0 = this.d.store.metadata[mid].z[0];
      t1 = this.d.store.metadata[mid].z[1];

      if ( (t0 >= this.tmetadata_gtimeline_tstart &&
            t0 <= this.tmetadata_gtimeline_tend) ||
           (t1 >= this.tmetadata_gtimeline_tstart &&
            t1 <= this.tmetadata_gtimeline_tend) ||
           (t0 <= this.tmetadata_gtimeline_tstart &&
            t1 >= this.tmetadata_gtimeline_tend)
         ) {
        this.tmetadata_gtimeline_mid[gid].push(mid);
      }
    }
  }
  this.tmetadata_gtimeline_mid[gid].sort( this._compare_mid_by_time.bind(this) );
}

//
// group timeline (common to all group-id and shown at top)
//
_via_temporal_segmenter.prototype._tmetadata_gtimeline_init = function() {
  this.gtimeline = document.createElement('canvas');
  this.gtimeline.setAttribute('class', 'gtimeline');
  this.gtimeline.addEventListener('mousedown', this._tmetadata_gtimeline_mousedown.bind(this));
  this.gtimeline.addEventListener('mouseup', this._tmetadata_gtimeline_mouseup.bind(this));
  this.gtimeline.addEventListener('mousemove', this._tmetadata_gtimeline_mousemove.bind(this));
  this.gtimeline.addEventListener('mouseleave', this._tmetadata_gtimeline_mouseleave.bind(this));

  this.gtimeline_pressed = false;
  this.gtimeline.width = this.timeline_container_width;
  this.gtimeline.height = this.linehn[this.GTIMELINE_HEIGHT];
  this.gtimelinectx = this.gtimeline.getContext('2d', {alpha:false});

  this.gtimeline.addEventListener('wheel', this._tmetadata_gtimeline_wheel_listener.bind(this));
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_wheel_listener = function(e) {
  if ( e.shiftKey ) {
    // pan temporal segment horizontally
    if (e.deltaY < 0) {
      this._tmetadata_boundary_move(this.TEMPORAL_SEG_MOVE_OFFSET);
    } else {
      this._tmetadata_boundary_move(-this.TEMPORAL_SEG_MOVE_OFFSET);
    }
  } else {
    // zoom temporal segment
    if (e.deltaY < 0) {
      this._tmetadata_gtimeline_zoomin();
    } else {
      this._tmetadata_gtimeline_zoomout();
    }
  }
  e.preventDefault();
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_zoomin = function() {
  var wps = this.tmetadata_width_per_sec + this.GTIMELINE_ZOOM_OFFSET;
  if ( wps < this.gtimeline.width ) {
    this.tmetadata_width_per_sec = wps;
    this._tmetadata_boundary_update(this.tmetadata_gtimeline_tstart);
    this._redraw_timeline();
    this._redraw_all();
  }
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_zoomout = function() {
  var wps = this.tmetadata_width_per_sec - this.GTIMELINE_ZOOM_OFFSET;
  if ( wps > 1 ) {
    this.tmetadata_width_per_sec = wps;
    this._tmetadata_boundary_update(this.tmetadata_gtimeline_tstart);
    this._redraw_timeline();
    this._redraw_all();
  }
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_clear = function() {
  this.gtimelinectx.fillStyle = '#ffffff';
  this.gtimelinectx.fillRect(0, 0, this.gtimeline.width, this.gtimeline.height);
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_draw = function() {
  this._tmetadata_gtimeline_clear();

  // draw line
  this.gtimelinectx.strokeStyle = '#707070';
  this.gtimelinectx.fillStyle = '#707070';
  this.gtimelinectx.lineWidth = this.DRAW_LINE_WIDTH;
  this.gtimelinectx.beginPath();
  this.gtimelinectx.moveTo(this.tmetadata_gtimeline_xstart, this.linehn[2]);
  this.gtimelinectx.lineTo(this.tmetadata_gtimeline_xend, this.linehn[2]);
  this.gtimelinectx.stroke();

  if ( this.tmetadata_gtimeline_mark_x.length ) {
    // draw tick labels
    this.gtimelinectx.fillStyle = '#666666';
    this.gtimelinectx.font = '9px Sans';
    var last_mark_x = this.tmetadata_gtimeline_mark_x[0];
    var tick_width = this.gtimelinectx.measureText(this.tmetadata_gtimeline_mark_time_str[0]).width;
    var dx;
    this.gtimelinectx.beginPath();
    for ( var i = 0; i < this.tmetadata_gtimeline_mark_x.length; ++i ) {
      dx = this.tmetadata_gtimeline_mark_x[i] - last_mark_x;
      if ( i === 0 || dx > tick_width ) {
        // draw tick
        this.gtimelinectx.moveTo(this.tmetadata_gtimeline_mark_x[i], this.linehn[2]);
        this.gtimelinectx.lineTo(this.tmetadata_gtimeline_mark_x[i], this.linehn[3]);

        // draw label
        this.gtimelinectx.fillText(this.tmetadata_gtimeline_mark_time_str[i],
                                   this.tmetadata_gtimeline_mark_x[i], this.linehn[4] );
        last_mark_x = this.tmetadata_gtimeline_mark_x[i];
      } else {
        // avoid crowding of labels
        continue;
      }
    }
    this.gtimelinectx.stroke();

    // draw marker for frames containing spatial regions
    this.gtimelinectx.fillStyle = 'black';
    this.gtimelinectx.beginPath();
    for ( var time in this.smid ) {
      if ( this.smid[time].length ) {
        var smarkx = this._tmetadata_gtimeline_time2canvas( parseFloat(time) );
        this.gtimelinectx.arc(smarkx, this.linehn[2] + 4, 4, 0, 2*Math.PI);
      }
    }
    this.gtimelinectx.fill();
  }

  // the remaining vertical space (at the bottom) is used to draw audio channel amplitude
  // drawing of audio visualization is triggered by _tmetadata_init() which in
  // turn continually triggers the _tmetadata_gtimeline_draw_audio() method
  // in _redraw_all()
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_draw_audio = function() {
  if(!this.m.paused && this.audio_analyser_active) {
    var x = this._tmetadata_gtimeline_time2canvas(this.m.currentTime);
    this.analyser.getFloatTimeDomainData(this.audio_data);
    var avg = 0.0;
    for(var i=0; i<this.audio_data.length; ++i) {
      avg += Math.abs(this.audio_data[i]);
    }
    avg = avg / this.audio_data.length;

    var max_height = (this.linehn[8] - this.linehn[4]);
    var y = Math.round( avg * max_height );
    this.gtimelinectx.fillStyle = '#4d4d4d';
    this.gtimelinectx.lineWidth = this.DRAW_LINE_WIDTH;
    this.gtimelinectx.fillRect(x, Math.max(this.linehn[4], this.linehn[6] - y), 2, 2*y);
  }
}

_via_temporal_segmenter.prototype._tmetadata_draw_currenttime_mark = function(tnow) {
  // clear previous mark
  this.gtimelinectx.fillStyle = '#ffffff';
  this.gtimelinectx.fillRect(0, 0, this.gtimeline.width, this.linehn[2] - 1);

  if ( tnow >= this.tmetadata_gtimeline_tstart &&
       tnow <= this.tmetadata_gtimeline_tend ) {
    var markx = this._tmetadata_gtimeline_time2canvas(tnow);
    this.gtimelinectx.fillStyle = 'black';
    this.gtimelinectx.beginPath();
    this.gtimelinectx.moveTo(markx, this.linehn[2]);
    this.gtimelinectx.lineTo(markx - this.linehb2, this.linehn[1]);
    this.gtimelinectx.lineTo(markx + this.linehb2, this.linehn[1]);
    this.gtimelinectx.moveTo(markx, this.linehn[2]);
    this.gtimelinectx.fill();
  }

  // show playback rate
  this.gtimelinectx.font = '10px Sans';
  var rate = this.m.playbackRate.toFixed(1) + 'X';
  this.gtimelinectx.fillText(rate, this.tmetadata_gtimeline_xend - this.linehn[2], this.linehn[2] - 2);
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_canvas2time = function(x) {
  var T = this.tmetadata_gtimeline_tend - this.tmetadata_gtimeline_tstart;
  var W = this.tmetadata_gtimeline_xend - this.tmetadata_gtimeline_xstart;
  var dx = x - this.padx;
  return this.tmetadata_gtimeline_tstart + ((T * dx) / W);
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_time2canvas = function(t) {
  var canvas_x;
  if ( t < this.tmetadata_gtimeline_tstart ||
       t > this.tmetadata_gtimeline_tend ) {
    // clamp to canvas boundary
    if ( t < this.tmetadata_gtimeline_tstart ) {
      canvas_x = this.tmetadata_gtimeline_xstart;
    } else {
      canvas_x = this.tmetadata_gtimeline_xend;
    }
  } else {
    var sec = Math.floor(t);
    var sec_mark_index = sec - this.tmetadata_gtimeline_tstart;
    var ms = t - sec;
    var ms_x = Math.ceil(ms * this.tmetadata_width_per_sec);
    canvas_x = this.tmetadata_gtimeline_mark_x[sec_mark_index] + ms_x;
  }
  return canvas_x;
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_mousemove= function(e) {
  var t = this._tmetadata_gtimeline_canvas2time(e.offsetX);
  if(this.gtimeline_pressed) {
    // Mouse is pressed. Seek to time t
    this.gtimeline.style.cursor = 'col-resize';
    this.m.currentTime = t;
    return;
  }
  var smark_time_str = this._tmetadata_gtimeline_is_on_smark(t);
  if ( smark_time_str !== '' ) {
    this.gtimeline.style.cursor = 'pointer';
    var suffix = this.smid[smark_time_str].length + ' region';
    if ( this.smid[smark_time_str].length > 1 ) {
      suffix += 's';
    }
    this.gtimeline.setAttribute('title',
                                'Click to jump to this video frame containing ' + suffix);
  } else {
    this.gtimeline.style.cursor = 'default';
  }
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_mousedown = function(e) {
  var t = this._tmetadata_gtimeline_canvas2time(e.offsetX);
  var smark_time_str = this._tmetadata_gtimeline_is_on_smark(t);
  if ( smark_time_str !== '' ) {
    // draw all regions associated with this frame
    this.m.currentTime = parseFloat(smark_time_str);
    this.emit_event('edit_frame_regions', { 't': parseFloat(smark_time_str),
                                            'mid_list': this.smid[smark_time_str],
                                          });
  } else {
    this.m.currentTime = t;
  }
  this.gtimeline_pressed = true;
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_mouseup = function(e) {
  this.gtimeline_pressed = false;
  this.gtimeline.style.cursor = 'default';
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_mouseleave = function(e) {
  this.gtimeline_pressed = false;
  this.gtimeline.style.cursor = 'default';
}

_via_temporal_segmenter.prototype._tmetadata_gtimeline_is_on_smark = function(t) {
  var smark_time;
  var zoom_factor = this.linehn[this.DEFAULT_WIDTH_PER_SEC] / this.tmetadata_width_per_sec;
  var mouse_tolerance = this.GTIMELINE_REGION_MARKER_MOUSE_TOL * zoom_factor;
  for ( var smark_time_str in this.smid ) {
    smark_time = parseFloat(smark_time_str);
    if ( Math.abs(smark_time - t) <= mouse_tolerance ) {
      return smark_time_str;
    }
  }
  return '';
}

//
// metadata for a given group-id
//
_via_temporal_segmenter.prototype._tmetadata_group_update_gid = function(e) {
  var old_gid = e.target.dataset.gid;
  var new_gid = e.target.value.trim();
  var mindex, mid;
  var av_update_list = [];
  for ( mindex in this.group[old_gid] ) {
    mid = this.group[old_gid][mindex]
    av_update_list.push( {'mid':mid,
                          'aid':this.groupby_aid,
                          'avalue':new_gid,
                         } );
  }

  this.d.metadata_update_av_bulk(this.vid, av_update_list).then( function(ok) {
    this.group[new_gid] = this.group[old_gid];
    delete this.group[old_gid];
    var gindex = this.gid_list.indexOf(old_gid);
    this.gid_list[gindex] = new_gid;

    this._tmetadata_gmetadata_update();
    delete this.tmetadata_gtimeline_mid[old_gid];
    this._tmetadata_boundary_fetch_gid_mid(new_gid);
    this._tmetadata_group_gid_draw(new_gid);

    // update selection to new_gid
    if ( this.selected_gid === old_gid ) {
      var new_gindex = this.gid_list.indexOf(new_gid);
      this._tmetadata_group_gid_sel(new_gindex);
    }
  }.bind(this), function(err) {
    console.warn('_via_data.metadata_update_av_bulk() failed');
  }.bind(this));
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_init = function(gid) {
  this.gcanvas[gid] = document.createElement('canvas');
  this.gcanvas[gid].setAttribute('data-gid', gid);
  this.gcanvas[gid].setAttribute('data-gindex', this.gid_list.indexOf(gid));
  this.gcanvas[gid].width = this.timeline_container_width;
  this.gcanvas[gid].height = this.linehn[4];
  this.gctx[gid] = this.gcanvas[gid].getContext('2d', {alpha:false});

  this.gcanvas[gid].addEventListener('mousemove', this._tmetadata_group_gid_mousemove.bind(this));
  this.gcanvas[gid].addEventListener('mousedown', this._tmetadata_group_gid_mousedown.bind(this));
  this.gcanvas[gid].addEventListener('mouseup', this._tmetadata_group_gid_mouseup.bind(this));
  this._tmetadata_group_gid_draw(gid);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_clear = function(gid) {
  if ( gid === this.selected_gid ) {
    this.gctx[gid].fillStyle = '#e6e6e6';
  } else {
    this.gctx[gid].fillStyle = '#ffffff';
  }
  this.gctx[gid].fillRect(0, 0, this.gcanvas[gid].width, this.gcanvas[gid].height);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_draw = function(gid) {
  this._tmetadata_group_gid_clear(gid);
  this._tmetadata_group_gid_draw_boundary(gid);
  this._tmetadata_group_gid_draw_metadata(gid);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_draw_boundary = function(gid) {
  var gindex = this.gid_list.indexOf(gid);
  var bcolor = this.COLOR_LIST[ gindex % this.NCOLOR ];
  // draw line
  this.gctx[gid].strokeStyle = bcolor;
  this.gctx[gid].fillStyle = '#ffffff';
  this.gctx[gid].lineWidth = this.DRAW_LINE_WIDTH;

  this.gctx[gid].beginPath();
  this.gctx[gid].moveTo(this.tmetadata_gtimeline_xstart, this.linehn[1]);
  this.gctx[gid].lineTo(this.tmetadata_gtimeline_xend, this.linehn[1]);
  this.gctx[gid].lineTo(this.tmetadata_gtimeline_xend, this.linehn[3]);
  this.gctx[gid].lineTo(this.tmetadata_gtimeline_xstart, this.linehn[3]);
  this.gctx[gid].lineTo(this.tmetadata_gtimeline_xstart, this.linehn[1]);
  this.gctx[gid].stroke();
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_mark_selected = function() {
  var gid = this.selected_gid;
  var mid = this.selected_mid;
  var t0, t1, x0, x1;
  t0 = this.d.store.metadata[mid].z[0];
  t1 = this.d.store.metadata[mid].z[1];
  x0 = this._tmetadata_gtimeline_time2canvas(t0);
  x1 = this._tmetadata_gtimeline_time2canvas(t1);

  // draw arrow extending to the edges of temporal segment
  this.gctx[gid].fillStyle = '#f2f2f2';
  this.gctx[gid].strokeStyle = '#e6e6e6';

  this.gctx[gid].lineWidth = this.DRAW_LINE_WIDTH;
  this.gctx[gid].beginPath();
  this.gctx[gid].moveTo(x0 + 2, this.linehn[2]);
  this.gctx[gid].lineTo(x0 + this.linehb2 + 2, this.linehn[2] - this.linehb2);
  this.gctx[gid].lineTo(x0 + this.linehb2 + 2, this.linehn[2] + this.linehb2);
  this.gctx[gid].lineTo(x0 + 2, this.linehn[2]);
  this.gctx[gid].lineTo(x1 - 1, this.linehn[2]);
  this.gctx[gid].lineTo(x1 - this.linehb2 - 1, this.linehn[2] - this.linehb2);
  this.gctx[gid].lineTo(x1 - this.linehb2 - 1, this.linehn[2] + this.linehb2);
  this.gctx[gid].lineTo(x1 - 1, this.linehn[2]);
  this.gctx[gid].stroke();
  this.gctx[gid].fill();

  // draw edge time if an edge is being updated
  if ( this.edge_show_time !== -1 ) {
    this.gctx[gid].font = '10px Sans';
    this.gctx[gid].fillStyle = '#000000';

    var time_str = this.d.store.metadata[this.selected_mid].z[this.edge_show_time].toFixed(3);
    if ( this.edge_show_time === 0 ) {
      this.gctx[gid].fillText(time_str, x0 + 1, this.linehn[4]);
    } else {
      var twidth = this.gctx[gid].measureText(time_str).width;
      this.gctx[gid].fillText(time_str, x1 - twidth, this.linehn[4]);
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_draw_metadata = function(gid) {
  if ( this.tmetadata_gtimeline_mid.hasOwnProperty(gid) ) {
    var mindex, mid;
    for ( mindex in this.tmetadata_gtimeline_mid[gid] ) {
      mid = this.tmetadata_gtimeline_mid[gid][mindex];
      this._tmetadata_group_gid_draw_mid(gid, mid);
    }

    if ( this.selected_gid === gid &&
         this.selected_mindex !== -1
       ) {
      this._tmetadata_group_gid_mark_selected();
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_draw_mid = function(gid, mid) {
  var t0, t1, x0, x1;
  t0 = this.d.store.metadata[mid].z[0];
  t1 = this.d.store.metadata[mid].z[1];
  x0 = this._tmetadata_gtimeline_time2canvas(t0);
  x1 = this._tmetadata_gtimeline_time2canvas(t1);

  // draw metadata block
  if ( gid === this.selected_gid &&
       mid === this.selected_mid ) {
    this.gctx[gid].fillStyle = '#333333';
    if ( this.metadata_resize_is_ongoing || this.metadata_move_is_ongoing ) {
      if ( this.metadata_resize_is_ongoing ) {
        x0 = this.metadata_ongoing_update_x[0];
        x1 = this.metadata_ongoing_update_x[1];
      } else {
        x0 = this.metadata_ongoing_update_x[0] + this.metadata_move_dx;
        x1 = this.metadata_ongoing_update_x[1] + this.metadata_move_dx;
      }
    }
  } else {
    var gindex = this.gid_list.indexOf(gid);
    var bcolor = this.COLOR_LIST[ gindex % this.NCOLOR ];
    this.gctx[gid].fillStyle = bcolor;
    //this.gctx[gid].fillStyle = '#808080';
  }

  this.gctx[gid].beginPath();
  this.gctx[gid].moveTo(x0, this.linehn[1] + 1);
  this.gctx[gid].lineTo(x1, this.linehn[1] + 1);
  this.gctx[gid].lineTo(x1, this.linehn[3] - 1);
  this.gctx[gid].lineTo(x0, this.linehn[3] - 1);
  this.gctx[gid].lineTo(x0, this.linehn[1] + 1);
  this.gctx[gid].fill();

  // vertical lines for edge ref.
  this.gctx[gid].strokeStyle = '#000000';
  this.gctx[gid].beginPath();
  this.gctx[gid].moveTo(x0 + 2, this.linehn[1] - 2);
  this.gctx[gid].lineTo(x0, this.linehn[1] - 2);
  this.gctx[gid].lineTo(x0, this.linehn[3] + 2);
  this.gctx[gid].lineTo(x0+2, this.linehn[3] + 2);
  this.gctx[gid].moveTo(x1 - 2, this.linehn[1] - 2);
  this.gctx[gid].lineTo(x1, this.linehn[1] - 2);
  this.gctx[gid].lineTo(x1, this.linehn[3] + 2);
  this.gctx[gid].lineTo(x1 - 2, this.linehn[3] + 2);
  this.gctx[gid].stroke();
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_sel = function(gindex) {
  var old_selected_gindex = this.selected_gindex;

  this.selected_gindex = gindex;
  this.selected_gid = this.gid_list[this.selected_gindex];
  this.selected_mindex = -1;
  this.selected_mid = '';

  this.edge_show_time = -1;
  this._tmetadata_group_gid_draw(this.selected_gid);
  this.gcanvas[this.selected_gid].scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"});

  // update old selection
  if ( old_selected_gindex !== -1 ) {
    if ( this.gid_list.hasOwnProperty(old_selected_gindex) ) {
      var old_selected_gid = this.gid_list[old_selected_gindex];
      this._tmetadata_group_gid_draw(old_selected_gid);
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_sel_metadata = function(mindex) {
  var old_selected_mindex = this.selected_mindex;
  this.selected_mindex = mindex;
  this.selected_mid = this.tmetadata_gtimeline_mid[this.selected_gid][mindex];
  this.m.currentTime = this.d.store.metadata[this.selected_mid].z[0];
  this._tmetadata_group_gid_draw(this.selected_gid);
  _via_util_msg_show('Metadata selected. ' +
                     'Use <span class="key">l</span> or <span class="key">r</span> to expand and ' +
                     '<span class="key">L</span> or <span class="key">R</span> to reduce segment. ' +
                     'Press <span class="key">&larr;</span>&nbsp;<span class="key">&rarr;</span> to move, ' +
                     '<span class="key">Backspace</span> to delete and ' +
                     '<span class="key">Esc</span> to unselect.', true);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_remove_mid_sel = function(mindex) {
  this.selected_mindex = -1;
  this.selected_mid = '';
  this.edge_show_time = -1;
  this._tmetadata_group_gid_draw(this.selected_gid);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_sel_metadata_at_time = function() {
  var t = this.m.currentTime;
  var mindex = this._tmetadata_group_gid_metadata_at_time(t);
  if ( mindex !== -1 ) {
    this._tmetadata_group_gid_sel_metadata(mindex);
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_metadata_at_time = function(t) {
  var mindex, mid;
  for ( mindex in this.tmetadata_gtimeline_mid[this.selected_gid] ) {
    mid = this.tmetadata_gtimeline_mid[this.selected_gid][mindex];
    if ( t >= this.d.store.metadata[mid].z[0] &&
         t <= this.d.store.metadata[mid].z[1]
       ) {
      return parseInt(mindex);
    }
  }
  return -1;
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_sel_next_metadata = function() {
  var sgid = this.gid_list[this.selected_gindex];
  if ( this.selected_mindex === -1 ) {
    if ( this.tmetadata_gtimeline_mid.hasOwnProperty(sgid) ) {
      if ( this.tmetadata_gtimeline_mid[sgid].length ) {
        // select first mid
        this._tmetadata_group_gid_sel_metadata(0);
      }
    }
  } else {
    var next_mindex = this.selected_mindex + 1;
    if ( next_mindex >= this.tmetadata_gtimeline_mid[sgid].length ) {
      next_mindex = 0;
    }
    this._tmetadata_group_gid_sel_metadata(next_mindex);
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_sel_prev_metadata = function() {
  var sgid = this.gid_list[this.selected_gindex];
  if ( this.selected_mindex === -1 ) {
    if ( this.tmetadata_gtimeline_mid.hasOwnProperty(sgid) ) {
      if ( this.tmetadata_gtimeline_mid[sgid].length ) {
        this._tmetadata_group_gid_sel_metadata(this.tmetadata_gtimeline_mid[sgid].length - 1);
      }
    }
  } else {
    var prev_mindex = this.selected_mindex - 1;
    if ( prev_mindex < 0 ) {
      prev_mindex =  this.tmetadata_gtimeline_mid[sgid].length - 1;
    }
    this._tmetadata_group_gid_sel_metadata(prev_mindex);
  }
}

_via_temporal_segmenter.prototype._tmetadata_mid_update_edge = function(eindex, dz) {
  this.edge_show_time = eindex;
  // to ensure only 3 decimal values are stored for time
  var new_value = this.d.store.metadata[this.selected_mid].z[eindex] + dz;
  new_value = _via_util_float_to_fixed(new_value, _VIA_FLOAT_FIXED_POINT);

  // consistency check
  if ( eindex === 0 ) {
    if ( new_value >= this.d.store.metadata[this.selected_mid].z[1] ) {
      _via_util_msg_show('Cannot update left edge!');
      return;
    }
  } else {
    if ( new_value <= this.d.store.metadata[this.selected_mid].z[0] ) {
      _via_util_msg_show('Cannot update right edge!');
      return;
    }
  }
  if ( new_value < 0.0 || new_value > this.m.duration ) {
    _via_util_msg_show('Cannot update edge beyond the boundary!');
    return;
  }

  this.d.metadata_update_zi(this.file.fid,
                            this.selected_mid,
                            eindex,
                            new_value
                           );
  this.m.currentTime = new_value;
  this._tmetadata_group_gid_draw(this.selected_gid);

}

_via_temporal_segmenter.prototype._tmetadata_mid_move = function(dt) {
  var n = this.d.store.metadata[this.selected_mid].z.length;
  var newz = this.d.store.metadata[this.selected_mid].z.slice();
  for ( var i = 0; i < n; ++i ) {
    newz[i] = parseFloat((parseFloat(newz[i]) + dt).toFixed(3));
    if ( newz[i] < 0 || newz[i] > this.m.duration ) {
      _via_util_msg_show('Cannot move temporal segment beyond the boundary!');
      return;
    }
  }
  this.d.metadata_update_z(this.vid, this.selected_mid, newz).then( function(ok) {
    this.m.currentTime = this.d.store.metadata[this.selected_mid].z[0];

    // the move operation may have changed the sequential order of mid
    this._tmetadata_boundary_fetch_gid_mid(this.selected_gid);

    // check if we need to shift timeline to show the recently moved metadata
    if ( newz[0] <= this.tmetadata_gtimeline_tstart ||
         newz[1] >= this.tmetadata_gtimeline_tend ) {
      var new_tstart = this.tmetadata_gtimeline_tstart - 1;
      if ( newz[1] >= this.tmetadata_gtimeline_tend ) {
        new_tstart = this.tmetadata_gtimeline_tstart + 1;
      }
      if ( new_tstart >= 0 ) {
        this._tmetadata_boundary_update(new_tstart)
        this._tmetadata_gtimeline_draw();
      }
    } else {
      this._tmetadata_group_gid_draw(this.selected_gid);
    }
  }.bind(this));
}

_via_temporal_segmenter.prototype._tmetadata_mid_del_sel = function(mid) {
  this._tmetadata_mid_del(this.selected_mid);
  this._tmetadata_group_gid_remove_mid_sel();
  this._tseg_metadata_hide();
  _via_util_msg_show('Temporal segment deleted.');
}

_via_temporal_segmenter.prototype._tmetadata_mid_del = function(mid) {
  var mindex = this.tmetadata_gtimeline_mid[this.selected_gid].indexOf(mid);
  if ( mindex !== -1 ) {
    this.tmetadata_gtimeline_mid[this.selected_gid].splice(mindex, 1);

    this._group_gid_del_mid(this.selected_gid, mid);
    this._tmetadata_group_gid_draw(this.selected_gid);
    this.d.metadata_delete(this.vid, mid);
  }
}

_via_temporal_segmenter.prototype._tmetadata_mid_update_last_added_end_edge_to_time = function(t) {
  if ( this.d.store.metadata.hasOwnProperty(this.metadata_last_added_mid) ) {
    var z_now = this.d.store.metadata[this.metadata_last_added_mid].z;
    var update_edge_index;
    if ( t > z_now[1] ) {
      update_edge_index = 1;
    } else {
      if ( t < z_now[0] ) {
        update_edge_index = 0;
      } else {
        var dt0 = Math.abs(z_now[0] - t);
        var dt1 = Math.abs(z_now[1] - t);
        if ( dt0 < dt1 ) {
          update_edge_index = 0;
        } else {
          update_edge_index = 1;
        }
      }
    }

    // to avoid malformed entried
    this.d.metadata_update_zi(this.file.fid,
                              this.metadata_last_added_mid,
                              update_edge_index,
                              t
                             );
    this._tmetadata_boundary_fetch_gid_mid(this.selected_gid);
    this._tmetadata_group_gid_draw(this.selected_gid);
  }
}

_via_temporal_segmenter.prototype._tmetadata_mid_add_at_time = function(t) {
  var z = [ t ];
  if ( z[0] < 0 ) {
    z[0] = 0.0;
  }

  z[1] = z[0] + this.DEFAULT_TEMPORAL_SEG_LEN;
  if ( z[1] > this.m.duration ) {
    z[1] = this.m.duration;
  }

  z = _via_util_float_arr_to_fixed(z, 3);
  // avoid adding same overlapping segments
  if ( this.d.store.metadata.hasOwnProperty(this.metadata_last_added_mid) ) {
    var z0 = this.d.store.metadata[this.metadata_last_added_mid].z;
    if ( z0[0] === z[0] && z0[1] === z[1] ) {
      _via_util_msg_show('A temporal segment of same size already exists.');
      return;
    }
  }
  var xy = [];
  var metadata = {};
  metadata[ this.groupby_aid ] = this.selected_gid;
  this.d.metadata_add(this.vid, z, xy, metadata).then( function(ok) {
    this.metadata_last_added_mid = ok.mid;
    this._tmetadata_group_gid_draw(this.selected_gid);
  }.bind(this), function(err) {
    _via_util_msg_show('Failed to add metadata!');
    console.log(err);
  }.bind(this));
}

_via_temporal_segmenter.prototype._tmetadata_mid_merge = function(eindex) {
  var merge_mid;
  if ( eindex === 0 ) {
    var left_mindex = parseInt(this.selected_mindex) - 1;
    if ( left_mindex >= 0 ) {
      merge_mid = this.tmetadata_gtimeline_mid[this.selected_gid][left_mindex];
    }
  } else {
    var right_mindex = parseInt(this.selected_mindex) + 1;
    if (right_mindex < this.tmetadata_gtimeline_mid[this.selected_gid].length ) {
      merge_mid = this.tmetadata_gtimeline_mid[this.selected_gid][right_mindex];
    }
  }

  if ( typeof(merge_mid) !== 'undefined' ) {
    var new_z = this.d.store.metadata[this.selected_mid].z;
    if ( new_z[0] > this.d.store.metadata[merge_mid].z[0] ) {
      new_z[0] = this.d.store.metadata[merge_mid].z[0];
    }
    if ( new_z[1] < this.d.store.metadata[merge_mid].z[1] ) {
      new_z[1] = this.d.store.metadata[merge_mid].z[1];
    }

    this._tmetadata_mid_del(merge_mid);
    // while selected mid remains consistent, mindex changes due to deletion
    this.selected_mindex = this.tmetadata_gtimeline_mid[this.selected_gid].indexOf(this.selected_mid);
    this.d.metadata_update_z(this.file.fid, this.selected_mid, new_z);
    this._tmetadata_group_gid_draw(this.selected_gid);
    if ( eindex === 0 ) {
      this.m.currentTime = new_z[0];
    } else {
      this.m.currentTime = new_z[1];
    }
    _via_util_msg_show('Temporal segments merged.');
  } else {
    _via_util_msg_show('Merge is not possible without temporal segments in the neighbourhood');
  }
}

_via_temporal_segmenter.prototype._tmetadata_mid_change_gid = function(from_gindex,
                                                                       to_gindex) {
  var from_gid = this.gid_list[from_gindex];
  var to_gid = this.gid_list[to_gindex];
  var mid_to_move = this.tmetadata_gtimeline_mid[from_gid][this.selected_mindex];
  var mindex_to_move = this.selected_mindex;

  // update metadata
  this.d.store.metadata[mid_to_move].av[this.groupby_aid] = to_gid;

  this._tmetadata_group_gid_remove_mid_sel(mindex_to_move);
  this.tmetadata_gtimeline_mid[from_gid].splice(mindex_to_move, 1);
  var from_group_mindex = this.group[from_gid].indexOf(mid_to_move);
  this.group[from_gid].splice(from_group_mindex, 1);
  this.group[to_gid].push(mid_to_move);

  this.tmetadata_gtimeline_mid[to_gid].push(mid_to_move);
  this.tmetadata_gtimeline_mid[to_gid].sort( this._compare_mid_by_time.bind(this) );
  var moved_mindex = this.tmetadata_gtimeline_mid[to_gid].indexOf(mid_to_move);

  this._tmetadata_group_gid_sel(to_gindex);
  this._tmetadata_group_gid_sel_metadata(moved_mindex);
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_mousemove = function(e) {
  var x = e.offsetX;
  var gid = e.target.dataset.gid;
  var t = this._tmetadata_gtimeline_canvas2time(x);
  if ( this.metadata_resize_is_ongoing ) {
    this.m.currentTime = t;
    // @todo: shown thumbnail
    //this._thumbview_show(t, 0, 0);
    this.metadata_ongoing_update_x[ this.metadata_resize_edge_index ] = x;
    this._tmetadata_group_gid_draw(gid);
    return;
  }

  if ( this.metadata_move_is_ongoing ) {
    this.metadata_move_dx = x - this.metadata_move_start_x;
    this._tmetadata_group_gid_draw(gid);
    return;
  }


  var check = this._tmetadata_group_gid_is_on_edge(gid, t);
  if ( check[0] !== -1 ) {
    this.gcanvas[gid].style.cursor = 'ew-resize';
  } else {
    this.gcanvas[gid].style.cursor = 'default';
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_mousedown = function(e) {
  var x = e.offsetX;
  var gid = e.target.dataset.gid;
  var gindex = e.target.dataset.gindex;
  var t = this._tmetadata_gtimeline_canvas2time(x);

  if ( gindex !== this.selected_gindex ) {
    // select this gid
    this._tmetadata_group_gid_sel(gindex);
    this._tseg_metadata_hide();
  }

  var edge = this._tmetadata_group_gid_is_on_edge(gid, t);
  if ( edge[0] !== -1 ) {
    // mousedown was at the edge
    this.metadata_resize_is_ongoing = true;
    this._tmetadata_group_gid_sel_metadata(edge[0]);
    this.metadata_resize_edge_index = edge[1];
    var z = this.d.store.metadata[this.selected_mid].z;
    this.metadata_ongoing_update_x = [ this._tmetadata_gtimeline_time2canvas(z[0]),
                                       this._tmetadata_gtimeline_time2canvas(z[1])
                                     ];
  } else {
    var mindex = this._tmetadata_group_gid_get_mindex_at_time(gid, t);
    if ( mindex !== -1 ) {
      // clicked on a metadata
      if ( this.selected_mindex !== -1 &&
           mindex === this.selected_mindex
         ) {
        // clicked on an already selected metadata
        // hence, start moving
        this.metadata_move_start_x = x;
        this.metadata_move_is_ongoing = true;
        var z = this.d.store.metadata[this.selected_mid].z;
        this.metadata_ongoing_update_x = [ this._tmetadata_gtimeline_time2canvas(z[0]),
                                           this._tmetadata_gtimeline_time2canvas(z[1])
                                         ];
      } else {
        // else, select metadata
        this._tmetadata_group_gid_sel_metadata(mindex);
        this._tseg_metadata_show();
      }
    } else {
      if ( this.selected_mindex !== -1 ) {
        // remove metadata selection
        this._tmetadata_group_gid_remove_mid_sel(this.selected_mindex);
        this._tseg_metadata_hide();
      }
    }
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_mouseup = function(e) {
  var x = e.offsetX;
  if ( this.metadata_resize_is_ongoing ) {
    // resize metadata
    var t = this._tmetadata_gtimeline_canvas2time(e.offsetX);
    var dz = t - this.d.store.metadata[this.selected_mid].z[this.metadata_resize_edge_index];
    this._tmetadata_mid_update_edge(this.metadata_resize_edge_index, dz);
    this.metadata_resize_is_ongoing = false;
    this.metadata_resize_edge_index = -1;
    this.metadata_ongoing_update_x = [0, 0];
    return;
  }

  if ( this.metadata_move_is_ongoing ) {
    var dx = x - this.metadata_move_start_x;
    if ( Math.abs(dx) > this.DRAW_LINE_WIDTH ) {
      var dt = dx / this.tmetadata_width_per_sec;
      this._tmetadata_mid_move(dt);
    }
    this.metadata_move_is_ongoing = false;
    this.metadata_ongoing_update_x = [0, 0];
    this._tmetadata_group_gid_draw(this.selected_gid);
    return;
  }
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_is_on_edge = function(gid, t) {
  var mindex, mid;
  for ( mindex in this.tmetadata_gtimeline_mid[gid] ) {
    mid = this.tmetadata_gtimeline_mid[gid][mindex];
    if ( Math.abs(t - this.d.store.metadata[mid].z[0]) < this.METADATA_EDGE_TOL ) {
      return [parseInt(mindex), 0];
    } else {
      if ( Math.abs(t - this.d.store.metadata[mid].z[1]) < this.METADATA_EDGE_TOL ) {
        return [parseInt(mindex), 1];
      }
    }
  }
  return [-1, -1];
}

_via_temporal_segmenter.prototype._tmetadata_group_gid_get_mindex_at_time = function(gid, t) {
  var mindex, mid;
  for ( mindex in this.tmetadata_gtimeline_mid[gid] ) {
    mid = this.tmetadata_gtimeline_mid[gid][mindex];
    if ( t >= this.d.store.metadata[mid].z[0] &&
         t <= this.d.store.metadata[mid].z[1]
       ) {
      return parseInt(mindex);
    }
  }
  return -1;
}

//
// keyboard input handler
//
_via_temporal_segmenter.prototype._on_event_keydown = function(e) {
  if(!this.is_init_success) {
    return;
  }

  // play/pause
  if ( e.key === ' ' ) {
    e.preventDefault();
    if( this.show_audio && !this.audio_analyser_active) {
      this._tmetadata_audio_init();
    }
    if ( this.m.paused ) {
      this.m.play();
      _via_util_msg_show('Playing ...');
    } else {
      this.m.pause();
      _via_util_msg_show('Paused. Press <span class="key">a</span> to add a temporal segment, ' +
                         '<span class="key">Tab</span> to select and ' +
                         '<span class="key">&uarr;</span>&nbsp;<span class="key">&darr;</span> to select another temporal segment timeline.', true);
    }
  }

  // jump 1,...,9 seconds forward or backward
  if ( ['1','2','3','4','5','6','7','8','9'].includes( e.key ) ) {
    if ( e.altKey ) {
      return; // Alt + Num is used to navigated browser tabs
    }
    e.preventDefault();
    var t = this.m.currentTime;
    if ( e.ctrlKey ) {
      t += parseInt(e.key);
    } else {
      t -= parseInt(e.key);
    }
    // clamp
    t = Math.max(0, Math.min(t, this.m.duration));
    this.m.pause();
    this.m.currentTime = t;
    return;
  }

  if ( e.key === 'N' || e.key === 'P') {
    e.preventDefault();
    if ( ! this.m.paused ) {
      return;
    }

    var t = this.m.currentTime;
    if ( e.key === 'N') {
      t = t + this.EDGE_UPDATE_TIME_DELTA * 5;
    } else {
      t = t - this.EDGE_UPDATE_TIME_DELTA * 5;
    }
    t = Math.max(0, Math.min(t, this.m.duration));
    this.m.currentTime = t;
    return;
  }

  if ( e.key === 's' || e.key === 'S' ) {
    e.preventDefault();
    this.m.pause();
    if ( e.key === 's' ) {
      if ( this.selected_mindex !== -1 ) {
        this.m.currentTime = this.d.store.metadata[this.selected_mid].z[0];
      } else {
        this.m.currentTime = this.tmetadata_gtimeline_tstart;
      }
    } else {
      this.m.currentTime = 0;
    }
    return;
  }

  if ( e.key === 'e' || e.key === 'E' ) {
    e.preventDefault();
    this.m.pause();
    if ( e.key === 'e' ) {
      if ( this.selected_mindex !== -1 ) {
        this.m.currentTime = this.d.store.metadata[this.selected_mid].z[1];
      } else {
        this.m.currentTime = this.tmetadata_gtimeline_tend;
      }
    } else {
      this.m.currentTime = this.m.duration - 1;
    }
    return;
  }

  // change playback rate
  if ( e.key === '0' ) {
    e.preventDefault();
    this.m.playbackRate = 1;
    return;
  }
  if ( e.key === '+' ) {
    if ( ! e.ctrlKey ) {
      e.preventDefault();
      this.m.playbackRate = this.m.playbackRate + 0.1;
    }
    return;
  }
  if ( e.key === '-' ) {
    if ( ! e.ctrlKey ) {
      e.preventDefault();
      if ( this.m.playbackRate > 0.1 ) {
        this.m.playbackRate = this.m.playbackRate - 0.1;
      }
    }
    return;
  }

  if ( (e.key === 'a' || e.key === 'A') && ! e.ctrlKey ) {
    e.preventDefault();
    var t = this.m.currentTime;
    if ( e.key === 'a' ) {
      this._tmetadata_mid_add_at_time(t);
    } else {
      if ( e.key === 'A' && e.shiftKey ) {
        this._tmetadata_mid_update_last_added_end_edge_to_time(t);
      }
    }
    return;
  }

  if ( e.key === 'Backspace' ) {
    e.preventDefault();
    if ( this.selected_mindex !== -1 ) {
      this._tmetadata_mid_del_sel();
    }
    return;
  }

  if ( e.key === 'm' ) {
    e.preventDefault();
    if ( this.m.muted ) {
      this.m.muted = false;
    } else {
      this.m.muted = true;
    }
    return;
  }

  if ( e.key === 'l' || e.key === 'L') {
    e.preventDefault();
    if ( this.selected_mindex !== -1 ) {
      // resize left edge of selected temporal segment
      var delta = this.EDGE_UPDATE_TIME_DELTA;
      if ( e.ctrlKey ) {
        delta = 1;
      }

      if ( e.key === 'l' ) {
        this._tmetadata_mid_update_edge(0, -delta);
      } else {
        this._tmetadata_mid_update_edge(0, delta);
      }
      return;
    } else {
      if ( e.key === 'l' ) {
        this.m.currentTime = this.m.currentTime - this.EDGE_UPDATE_TIME_DELTA;
      } else {
        this.m.currentTime = this.m.currentTime - 2*this.EDGE_UPDATE_TIME_DELTA;
      }
    }
  }

  if ( e.key === 'r' || e.key === 'R') {
    e.preventDefault();
    if ( this.selected_mindex !== -1 ) {
      // resize left edge of selected temporal segment
      var delta = this.EDGE_UPDATE_TIME_DELTA;
      if ( e.ctrlKey ) {
        delta = 1;
      }

      if ( e.key === 'r' ) {
        this._tmetadata_mid_update_edge(1, delta);
      } else {
        this._tmetadata_mid_update_edge(1, -delta);
      }
      return;
    } else {
      if ( e.key === 'r' ) {
        this.m.currentTime = this.m.currentTime + this.EDGE_UPDATE_TIME_DELTA;
      } else {
        this.m.currentTime = this.m.currentTime + 2*this.EDGE_UPDATE_TIME_DELTA;
      }
    }
  }

  // cancel ongoing action or event
  if ( e.key === 'Escape' ) {
    e.preventDefault();
    this._tmetadata_group_gid_remove_mid_sel();
    return;
  }

  // select temporal segments
  if ( e.key === 'Tab' ) {
    e.preventDefault();

    if ( e.shiftKey ) {
      this._tmetadata_group_gid_sel_prev_metadata();
    } else {
      this._tmetadata_group_gid_sel_next_metadata();
    }
    return;
  }

  if ( e.key === 'Enter' ) {
    e.preventDefault();
    this.m.pause();
    this._tmetadata_group_gid_sel_metadata_at_time();
    return;
  }

  if ( e.key === 'ArrowDown' || e.key === 'ArrowUp' ) {
    e.preventDefault();

    // determine the current and new gid
    var selected_gindex = this.gid_list.indexOf(this.selected_gid);
    var next_gindex;
    if ( e.key === 'ArrowDown' ) {
      next_gindex = selected_gindex + 1;
      if ( next_gindex >= this.gid_list.length ) {
        next_gindex = 0;
      }
    } else {
      next_gindex = selected_gindex - 1;
      if ( next_gindex < 0 ) {
        next_gindex = this.gid_list.length - 1;
      }
    }

    if(e.ctrlKey) {
      // change the order of timeline entries by moving a timeline
      // to the position of next/prev timeline
      this._tmetadata_gid_list_reorder(selected_gindex, next_gindex);
    } else {
      // select next/prev timeline or move selected segment to next/prev timeline

      if(this.selected_mindex !== -1 && this.selected_mid !== '') {
        // move selected temporal segment to the timeline present above/below the current timeline
        var from_gindex = selected_gindex;
        var to_gindex = next_gindex;
        this._tmetadata_mid_change_gid(from_gindex, to_gindex);
      } else {
        // selected the group above/below the current group in the timeline list
        this._tmetadata_group_gid_sel(next_gindex);
        _via_util_msg_show('Selected group "' + this.selected_gid + '"');
        return;
      }
    }
  }

  if ( e.key === 'ArrowLeft' || e.key === 'ArrowRight' ) {
    e.preventDefault();
    if ( this.selected_mindex !== -1 ) {
      if ( e.shiftKey ) {
        // merge current region with left or right metadata
        if ( e.key === 'ArrowLeft' ) {
          this._tmetadata_mid_merge(0);
        } else {
          this._tmetadata_mid_merge(1);
        }
      } else {
        // move selected temporal segment
        var delta = this.EDGE_UPDATE_TIME_DELTA;
        if ( e.ctrlKey ) {
          delta = 1.0;
        }
        if ( e.key === 'ArrowLeft' ) {
          this._tmetadata_mid_move(-delta);
        } else {
          this._tmetadata_mid_move(delta);
        }
      }
    } else {
      // move temporal seg. timeline
      if(e.shiftKey) {
        // shift visible timeline by 60 sec.
        if ( e.key === 'ArrowLeft' ) {
          this._tmetadata_boundary_move(-60*this.TEMPORAL_SEG_MOVE_OFFSET);
        } else {
          this._tmetadata_boundary_move(60*this.TEMPORAL_SEG_MOVE_OFFSET);
        }
      } else {
        // shift by 1 frame
        var t = this.m.currentTime;
        if ( e.key === 'ArrowLeft' ) {
          t = t - this.EDGE_UPDATE_TIME_DELTA;
        } else {
          t = t + this.EDGE_UPDATE_TIME_DELTA;
        }
        t = Math.max(0, Math.min(t, this.m.duration));
        this.m.currentTime = t;
      }
    }
    return;
  }

  if ( e.key === 'F2' ) {
    e.preventDefault();
    _via_util_show_info_page('keyboard_shortcuts');
    return;
  }
}

//
// group by
//

_via_temporal_segmenter.prototype._group_init = function(aid) {
  this.group = {};
  this.groupby_aid = aid;

  var mid, avalue;
  for ( var mindex in this.d.cache.mid_list[this.vid] ) {
    mid = this.d.cache.mid_list[this.vid][mindex];
    if ( this.d.store.metadata[mid].z.length >= 2 &&
         this.d.store.metadata[mid].xy.length === 0
       ) {
      if ( this.d.store.metadata[mid].av.hasOwnProperty(this.groupby_aid) ) {
        avalue = this.d.store.metadata[mid].av[this.groupby_aid];
        if ( ! this.group.hasOwnProperty(avalue) ) {
          this.group[avalue] = [];
        }
        this.group[avalue].push(mid);
      }
    }
  }

  // add possible values for the group variable
  this.gid_list = [];
  // if attribute type is select, then add gid from the attribute's options
  if ( this.d.store.attribute[aid].type === _VIA_ATTRIBUTE_TYPE.SELECT &&
       Object.keys(this.d.store.attribute[aid].options).length !== 0
     ) {
    for ( var gid in this.d.store.attribute[aid].options ) {
      if ( ! this.group.hasOwnProperty(gid) ) {
        this.group[gid] = [];
        this.gid_list.push(gid);
      }
    }
  }

  // add groups contained in metadata
  for ( var gid in this.group ) {
    if ( ! this.gid_list.includes(gid) ) {
      this.gid_list.push(gid);
    }
  }

  // for clarity, ensure that there is at least one group
  if ( this.gid_list.length === 0 ) {
    var default_gid = '_DEFAULT';
    this.group[default_gid] = [];
    this.gid_list.push(default_gid);
  }
  this.gid_list.sort(this._compare_gid.bind(this)); // sort by group-id

  // sort each group elements based on time
  var gid;
  for ( gid in this.group ) {
    this.group[gid].sort( this._compare_mid_by_time.bind(this) );
  }
}

_via_temporal_segmenter.prototype._compare_gid = function(gid1_str, gid2_str) {
  var gid1 = parseInt(gid1_str);
  var gid2 = parseInt(gid2_str);
  if(isNaN(gid1) || isNaN(gid2)) {
    // gid are not numbers and therefore sort them as string
    if(gid1_str < gid2_str) {
      return -1;
    } else {
      return 1;
    }
  } else {
    // gid are numbers and therefore sort them numerically
    if(gid1 < gid2) {
      return -1;
    } else {
      return 1;
    }
  }
}

_via_temporal_segmenter.prototype._compare_mid_by_time = function(mid1, mid2) {
  var t00 = this.d.store.metadata[mid1].z[0];
  var t10 = this.d.store.metadata[mid2].z[0];
  var t01 = this.d.store.metadata[mid1].z[1];
  var t11 = this.d.store.metadata[mid2].z[1];

  if ( typeof(t00) === 'string' ||
       typeof(t01) === 'string' ) {
    t00 = parseFloat(t00);
    t01 = parseFloat(t01);
    t10 = parseFloat(t10);
    t11 = parseFloat(t11);
  }

  if ( (t00 === t10) && ( t01 === t11 ) ) {
    return 0;
  }

  if ( ( t00 === t10 ) || ( t01 === t11 ) ) {
    var a,b;
    if ( ( t00 === t10 ) ) {
      // same start time
      a = t01;
      b = t11;
    } else {
      // same end time
      a = t00;
      b = t10;
    }
    if ( a < b ) {
      return -1;
    } else {
      return 1;
    }
  } else {
    if ( (t00 < t10) || ( t01 < t11 ) ) {
      return -1;
    } else {
      return 1;
    }
  }
}

_via_temporal_segmenter.prototype._group_gid_add_mid = function(gid, new_mid) {
  // find the best location
  var i, mindex, mid;
  for ( mindex in this.group[gid] ) {
    mid = this.group[gid][mindex];
    if ( this.d.store.metadata[new_mid].z[0] < this.d.store.metadata[mid].z[0] ) {
      this.group[gid].splice(mindex, 0, new_mid); // insert at correct location
      return;
    }
  }

  // if the insertion was not possible, simply push at the end
  this.group[gid].push(new_mid);
  this._tmetadata_group_gid_draw(gid);
}

_via_temporal_segmenter.prototype._group_gid_del_mid = function(gid, mid) {
  var mindex = this.group[gid].indexOf(mid);
  if ( mindex !== -1 ) {
    this.group[gid].splice(mindex, 1);
  }
  this._tmetadata_group_gid_draw(gid);
}

_via_temporal_segmenter.prototype._group_del_gid = function(gid_list) {
  return new Promise( function(ok_callback, err_callback) {
    try {
      var del_mid_list = [];
      var gid;
      for ( var i in gid_list ) {
        gid = gid_list[i];
        for ( var mindex in this.group[gid] ) {
          del_mid_list.push(this.group[gid][mindex]);
        }
        delete this.group[gid];
        delete this.tmetadata_gtimeline_mid[gid];
        var gindex = this.gid_list.indexOf(gid);
        this.gid_list.splice(gindex, 1);
        delete this.gctx[gid];
        delete this.gcanvas[gid];
      }
      ok_callback(del_mid_list);
    }
    catch(ex) {
      err_callback(ex);
    }
  }.bind(this));
}

_via_temporal_segmenter.prototype._group_add_gid = function(gid) {
  if ( this.group.hasOwnProperty(gid) ) {
    _via_util_msg_show(this.d.attribute_store[this.groupby_aid].aname +
                       ' [' + gid + '] already exists!');
  } else {
    this.group[gid] = [];
    this.gid_list.push(gid);
    this.metadata_tbody.appendChild( this._tmetadata_group_gid_html(gid) );
    this.new_group_id_input.value = ''; // clear input field
    _via_util_msg_show('Add ' + this.d.attribute_store[this.groupby_aid].aname +
                       ' [' + gid + ']');
  }
}

//
// Utility functions
//
_via_temporal_segmenter.prototype._time2str = function(t) {
  var hh = Math.floor(t / 3600);
  var mm = Math.floor( (t - hh * 3600) / 60 );
  var ss = Math.round( t - hh*3600 - mm*60 );
  if ( hh < 10 ) {
    hh = '0' + hh;
  }
  if ( mm < 10 ) {
    mm = '0' + mm;
  }
  if ( ss < 10 ) {
    ss = '0' + ss;
  }
  return hh + ':' + mm + ':' + ss;
}

_via_temporal_segmenter.prototype._time2strms = function(t) {
  var hh = Math.floor(t / 3600);
  var mm = Math.floor( (t - hh * 3600) / 60 );
  var ss = Math.floor( t - hh*3600 - mm*60 );
  var ms = Math.round( (t - Math.floor(t) ) * 1000 );
  if ( hh < 10 ) {
    hh = '0' + hh;
  }
  if ( mm < 10 ) {
    mm = '0' + mm;
  }
  if ( ss < 10 ) {
    ss = '0' + ss;
  }
  if ( ms < 100 ) {
    if ( ms < 10 ) {
      ms = '00' + ms;
    } else {
      ms = '0' + ms;
    }
  }
  return hh + ':' + mm + ':' + ss + '.' + ms;
}

_via_temporal_segmenter.prototype._time2ssms = function(t) {
  var hh = Math.floor(t / 3600);
  var mm = Math.floor( (t - hh * 3600) / 60 );
  var ss = Math.floor( t - hh*3600 - mm*60 );
  var ms = Math.floor( (t - Math.floor(t) ) * 1000 );
  return ss + ':' + ms;
}

_via_temporal_segmenter.prototype._vtimeline_playbackrate2str = function(t) {
  return this.m.playbackRate + 'X';
}

//
// external events
//
_via_temporal_segmenter.prototype._on_event_metadata_del = function(vid, mid) {
  _via_util_msg_show('Metadata deleted');
}

_via_temporal_segmenter.prototype._on_event_metadata_add = function(vid, mid) {
  if ( this.vid === vid &&
       this.d.store.metadata[mid].z.length === 2 &&
       this.d.store.metadata[mid].xy.length === 0
     ) {
    this._group_gid_add_mid(this.selected_gid, mid); // add at correct location
    this._tmetadata_boundary_fetch_gid_mid(this.selected_gid);
    _via_util_msg_show('Metadata added');
  }
}

_via_temporal_segmenter.prototype._on_event_metadata_update = function(vid, mid) {
  _via_util_msg_show('Metadata updated');
}

//
// Toolbar
//
_via_temporal_segmenter.prototype._toolbar_init = function() {
  this.toolbar_container = document.createElement('div');
  this.toolbar_container.setAttribute('class', 'toolbar_container');

  var pb_mode_container = document.createElement('div');
  var pb_mode_select = document.createElement('select');
  pb_mode_select.setAttribute('style', 'width:11em;');
  pb_mode_select.setAttribute('title', 'Normal playback shows media in the normal 1X speed. To review existing segments, use the Segment Review playback mode to speed up playback in the gap areas and normal playback in the segments.');
  pb_mode_select.addEventListener('change', this._toolbar_playback_mode_on_change.bind(this));
  var pb_mode_option_list = {'Normal':this.PLAYBACK_MODE.NORMAL,
                             'Segments Review':this.PLAYBACK_MODE.REVIEW_SEGMENT,
                             'Gaps Review':this.PLAYBACK_MODE.REVIEW_GAP
                            };
  for ( var pb_mode_name in pb_mode_option_list ) {
    var oi = document.createElement('option');
    oi.setAttribute('value', pb_mode_option_list[pb_mode_name]);
    oi.innerHTML = 'Playback: ' + pb_mode_name;
    pb_mode_select.appendChild(oi);
  }
  pb_mode_container.appendChild(pb_mode_select);

  var gtimeline_row_count_select = document.createElement('select');
  gtimeline_row_count_select.setAttribute('title', 'Number of timeline entries visible to the user at any time.');
  for(var row_count in this.fa.va.GTIMELINE_ROW_HEIGHT_MAP) {
    var option = document.createElement('option');
    option.setAttribute('value', row_count);
    if(row_count === this.d.store['config']['ui']['gtimeline_visible_row_count']) {
      option.setAttribute('selected', '');
    }
    option.innerHTML = row_count;
    gtimeline_row_count_select.appendChild(option);
  }
  gtimeline_row_count_select.addEventListener('change', function(e) {
    this.d.store['config']['ui']['gtimeline_visible_row_count'] = e.target.options[e.target.selectedIndex].value;
    this.fa.va.view_show(this.vid);
  }.bind(this));
  var gtimeline_height_container = document.createElement('div');
  gtimeline_height_container.appendChild(gtimeline_row_count_select);

  var keyboard_shortcut = document.createElement('span');
  keyboard_shortcut.setAttribute('class', 'text_button');
  keyboard_shortcut.innerHTML = 'Keyboard Shortcuts';
  keyboard_shortcut.addEventListener('click', function() {
    _via_util_page_show('page_keyboard_shortcut');
  });

  // tool to add/delete timeline
  var attrupdate_container = document.createElement('div');
  var add = document.createElement('button');
  add.innerHTML = 'Add';
  add.setAttribute('title', 'Add a new timeline');
  add.addEventListener('click', this._toolbar_gid_add.bind(this));

  var del = document.createElement('button');
  del.innerHTML = 'Del';
  del.setAttribute('title', 'Delete an existing timeline');
  del.addEventListener('click', this._toolbar_gid_del.bind(this));

  var attrval = document.createElement('input');
  attrval.setAttribute('class', 'newgid');
  attrval.setAttribute('id', 'gid_add_del_input');
  attrval.setAttribute('type', 'text');
  attrval.setAttribute('title', 'Name of timeline to add or delete');
  attrval.setAttribute('placeholder', 'add/del ' + this.d.store.attribute[this.groupby_aid].aname);
  attrupdate_container.appendChild(attrval)
  attrupdate_container.appendChild(add)
  attrupdate_container.appendChild(del)

  this.toolbar_container.appendChild(attrupdate_container);
  this.toolbar_container.appendChild(gtimeline_height_container);
  this.toolbar_container.appendChild(pb_mode_container);
  this.toolbar_container.appendChild(keyboard_shortcut);

  this.c.appendChild(this.toolbar_container);
}

_via_temporal_segmenter.prototype._toolbar_playback_mode_on_change = function(e) {
  this.current_playback_mode = e.target.value;
  if ( this.current_playback_mode === this.PLAYBACK_MODE.NORMAL ) {
    this._toolbar_playback_rate_set(1);
  }
}

_via_temporal_segmenter.prototype._toolbar_playback_rate_set = function(rate) {
  if ( this.m.playbackRate !== rate ) {
    this.m.playbackRate = rate;
  }
}

_via_temporal_segmenter.prototype._toolbar_gid_add = function() {
  var new_gid = document.getElementById('gid_add_del_input').value.trim();
  if(new_gid === '') {
    _via_util_msg_show('Name of new timeline cannot be empty. Enter the name of new timeline in the input panel.');
    return;
  }
  if ( this.group.hasOwnProperty(new_gid) ) {
    _via_util_msg_show('Timeline [' + new_gid + '] already exists!');
    return;
  }

  this.group[new_gid] = [];
  this.gid_list.push(new_gid);
  this._tmetadata_gmetadata_update();
  document.getElementById('gid_add_del_input').value = '';
}

_via_temporal_segmenter.prototype._toolbar_gid_del = function() {
  var del_gid = document.getElementById('gid_add_del_input').value.trim();
  if(del_gid === '') {
    _via_util_msg_show('To delete, you must provide the name of an existing timeline.');
    return;
  }

  if(!this.gid_list.includes(del_gid)) {
    _via_util_msg_show('Timeline [' + del_gid + '] does not exist and therefore deletion is not possible');
    return;
  }

  var del_gid_list = [del_gid];
  this._group_del_gid(del_gid_list).then( function(del_mid_list) {
    this.d.metadata_delete_bulk(this.vid, del_mid_list, false).then( function(ok) {
      this._tmetadata_gmetadata_update();
      document.getElementById('gid_add_del_input').value = '';
      _via_util_msg_show('Deleted timeline ' + JSON.stringify(del_gid_list) + ' and ' + del_mid_list.length + ' metadata associated with this timeline.');
    }.bind(this), function(err) {
      _via_util_msg_show('Failed to delete ' + del_mid_list.length + ' metadata associated with timeline ' + JSON.stringify(del_gid_list));
    }.bind(this));
  }.bind(this), function(err) {
    _via_util_msg_show('Failed to delete timeline ' + JSON.stringify(del_gid_list));
  }.bind(this));
}

//
// external events
//
_via_temporal_segmenter.prototype._on_event_attribute_update = function(data, event_payload) {
  var aid = event_payload.aid;
  if ( this.groupby_aid === aid ) {
    _via_util_msg_show('Attribute [' + this.d.store['attribute'][aid]['aname'] + '] updated.');
    this._init();
  }
}

_via_temporal_segmenter.prototype._on_event_metadata_update_bulk = function(data, event_payload) {
  if ( this.vid === event_payload.vid ) {
    _via_util_msg_show('Updated ' + event_payload.mid_list.length + ' metadata');
  }
}

//
// temporal segment metadata container
//
_via_temporal_segmenter.prototype._tseg_metadata_show = function() {
  this.tseg_metadata_container.classList.remove('hide');
  this._tseg_metadata_update();
  this._tseg_metadata_set_position();
}

_via_temporal_segmenter.prototype._tseg_metadata_hide = function() {
  this.tseg_metadata_container.classList.add('hide');
}

_via_temporal_segmenter.prototype._tseg_metadata_toggle = function() {
  this.d.store.config.ui['temporal_segment_metadata_editor_visible'] = ! this.d.store.config.ui['temporal_segment_metadata_editor_visible'];
  this._tseg_metadata_show();
  this._tseg_metadata_update();
  this._tseg_metadata_set_position();
}

_via_temporal_segmenter.prototype._tseg_metadata_update = function() {
  this.tseg_metadata_container.innerHTML = '';
  var tseg_aid_list = [];

  for(var aid in this.d.store.attribute) {
    if(this.d.store.attribute[aid]['anchor_id'] === 'FILE1_Z2_XY0' &&
       aid != this.groupby_aid) {
      tseg_aid_list.push(aid);
    }
  }

  var table = document.createElement('table');
  var tbody = document.createElement('tbody');
  var thead = document.createElement('thead');

  var header = document.createElement('tr');

  if ( this.d.store.config.ui['temporal_segment_metadata_editor_visible'] ) {
    var aid;
    var col_count = 0;
    for ( var aindex in tseg_aid_list ) {
      aid = tseg_aid_list[aindex];
      var th = document.createElement('th');
      th.innerHTML = this.d.store.attribute[aid].aname;
      header.appendChild(th);
      col_count += 1;
    }

    // show value of each attribute
    var tr = document.createElement('tr');
    tr.setAttribute('data-mid', mid);

    var mid = this.selected_mid;
    var aid;
    for ( var aindex in tseg_aid_list ) {
      aid = tseg_aid_list[aindex];
      var td = document.createElement('td');
      td.setAttribute('data-aid', aid);
      td.appendChild( this._tseg_metadata_attribute_io_html_element(mid, aid) );
      tr.appendChild(td);
    }
    var td = document.createElement('td');
    tr.appendChild(td); // empty row for control buttons

    tbody.appendChild(tr);
  }
  var minimise_button = document.createElement('span');
  minimise_button.setAttribute('class', 'text_button');
  if ( this.d.store.config.ui['temporal_segment_metadata_editor_visible'] ) {
    minimise_button.innerHTML = '&larr;';
    minimise_button.setAttribute('title', 'Hide (i.e. minimise) temporal segment metadata editor');
  } else {
    minimise_button.innerHTML = '&rarr;';
    minimise_button.setAttribute('title', 'Show temporal segment metadata editor');
  }
  minimise_button.addEventListener('click', this._tseg_metadata_toggle.bind(this));
  var minimise_container = document.createElement('th');
  minimise_container.setAttribute('rowspan', '2');
  minimise_container.appendChild(minimise_button);
  header.appendChild(minimise_container);
  col_count += 1;
  thead.appendChild(header);

  // add a row containing all the tool buttons
  var toolbar = document.createElement('tr');
  var toolbar_container = document.createElement('td');
  toolbar_container.setAttribute('colspan', tseg_aid_list.length + 1); // 1 extra column for minimise button
  toolbar_container.setAttribute('style', 'text-align:center;')

  var del = _via_util_get_svg_button('micon_delete', 'Delete selected temporal segment', 'del_selected_tset');
  del.addEventListener('click', this._tmetadata_mid_del_sel.bind(this));
  toolbar_container.appendChild(del);
  toolbar.appendChild(toolbar_container);
  tbody.appendChild(toolbar);

  table.appendChild(thead);
  table.appendChild(tbody);
  this.tseg_metadata_container.innerHTML = '';
  this.tseg_metadata_container.appendChild(table);
}

_via_temporal_segmenter.prototype._tseg_metadata_set_position = function() {
  var gid = this.selected_gid;
  var mid = this.selected_mid;

  var t0 = this.d.store.metadata[mid].z[0];
  var t1 = this.d.store.metadata[mid].z[1];
  var tseg_x0 = this._tmetadata_gtimeline_time2canvas(t0);
  var tseg_x1 = this._tmetadata_gtimeline_time2canvas(t1);

  var tseg_canvas_x = this.gcanvas[gid].offsetLeft;
  var tseg_canvas_y = this.gcanvas[gid].offsetTop;

  this.tseg_metadata_container.style.left = (tseg_canvas_x + tseg_x0) + 'px';
  this.tseg_metadata_container.style.top = (tseg_canvas_y - this.tseg_metadata_container.offsetHeight) + 'px';
}

_via_temporal_segmenter.prototype._tseg_metadata_header_html = function(aid_list) {
  var tr = document.createElement('tr');
  var aid;
  for ( var aindex in aid_list ) {
    aid = aid_list[aindex];
    var th = document.createElement('th');
    th.innerHTML = this.d.store.attribute[aid].aname;
    tr.appendChild(th);
  }
  return tr;
}

_via_temporal_segmenter.prototype._tseg_metadata_on_change = function(e) {
  var mid = e.target.dataset.mid;
  var aid = e.target.dataset.aid;
  var aval = e.target.value;
  if ( e.target.type === 'checkbox' &&
       this.d.store.metadata[mid].av.hasOwnProperty(aid)
     ) {
    var values = this.d.store.metadata[mid].av[aid].split(',');
    if ( this.d.store.metadata[mid].av[aid] !== '' ) {
      var vindex = values.indexOf(e.target.value);
      if ( e.target.checked ) {
        // add this value
        if ( vindex === -1 ) {
          values.push(e.target.value);
        }
      } else {
        // remove this value
        var vindex = values.indexOf(aval);
        if ( vindex !== -1 ) {
          values.splice(vindex, 1);
        }
      }
      aval = values.join(',');
    }
  }

  this.d.metadata_update_av(this.vid, mid, aid, aval).then( function(ok) {
    //console.log( JSON.stringify(this.d.store.metadata[ok.mid].av) );
  }.bind(this));
}

_via_temporal_segmenter.prototype._tseg_metadata_attribute_io_html_element = function(mid, aid) {
  var aval  = this.d.store.metadata[mid].av[aid];
  var dval  = this.d.store.attribute[aid].default_option_id;
  var atype = this.d.store.attribute[aid].type;
  var el;

  switch(atype) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }
    el = document.createElement('textarea');
    el.addEventListener('change', this._tseg_metadata_on_change.bind(this));
    el.innerHTML = aval;
    break;

  case _VIA_ATTRIBUTE_TYPE.SELECT:
    el = document.createElement('select');
    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }

    var option_selected = false;
    for ( var oid in this.d.store.attribute[aid].options ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', oid);
      oi.innerHTML = this.d.store.attribute[aid].options[oid];
      if ( oid === aval ) {
        oi.setAttribute('selected', 'true');
        option_selected = true;
      }
      el.appendChild(oi);
    }
    if(!option_selected) {
      el.selectedIndex = -1; // to indicate that nothing has been selected
    }
    el.addEventListener('change', this._tseg_metadata_on_change.bind(this));
    break;

  case _VIA_ATTRIBUTE_TYPE.RADIO:
    el = document.createElement('div');

    if ( typeof(aval) === 'undefined' ) {
      aval = dval;
    }

    for ( var oid in this.d.store.attribute[aid].options ) {
      var radio = document.createElement('input');
      radio.setAttribute('type', 'radio');
      radio.setAttribute('value', oid);
      radio.setAttribute('data-mid', mid);
      radio.setAttribute('data-aid', aid);
      radio.setAttribute('name', this.d.store.attribute[aid].aname);
      if ( oid === aval ) {
        radio.setAttribute('checked', true);
      }
      radio.addEventListener('change', this._tseg_metadata_on_change.bind(this));
      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(radio);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.CHECKBOX:
    el = document.createElement('div');
    if ( typeof(aval) === 'undefined' ) {
      if ( typeof(dval) === 'undefined' ) {
        aval = '';
      } else {
        aval = dval;
      }
    }
    var values = aval.split(',');
    for ( var oid in this.d.store.attribute[aid].options ) {
      var checkbox = document.createElement('input');
      checkbox.setAttribute('type', 'checkbox');
      checkbox.setAttribute('value', oid);
      checkbox.setAttribute('data-mid', mid);
      checkbox.setAttribute('data-aid', aid);
      checkbox.setAttribute('name', this.d.store.attribute[aid].aname);

      if ( values.indexOf(oid) !== -1 ) {
        checkbox.setAttribute('checked', true);
      }
      checkbox.addEventListener('change', this._tseg_metadata_on_change.bind(this));
      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(checkbox);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  default:
    console.log('attribute type ' + atype + ' not implemented yet!');
    var el = document.createElement('span');
    el.innerHTML = aval;
  }
  el.setAttribute('data-mid', mid);
  el.setAttribute('data-aid', aid);
  return el;
}
</script>
<!-- END: Contents of file: js/_via_temporal_segmenter.js-->
<!-- START: Contents of file: js/_via_view_annotator.js-->
<script>
/**
 * Builds user interface and control handlers to allow
 * annotation of a file (image, video or audio)

 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 24 Dec. 2018
 *
 */

'use strict';

const _VIA_VIEW_MODE = {'UNKNOWN':0,
                        'IMAGE1':1, 'IMAGE2':2, 'IMAGEK':3,
                        'VIDEO1':101, 'VIDEO2':102, 'VIDEOK':103,
                        'AUDIO1':201, 'AUDIO2':202, 'AUDIOK':203
                       };
const _VIA_PAGE = {
  'ABOUT':'page_about',
  'SHORTCUT':'page_shortcut',
  'START_INFO':'page_start_info',
};

function _via_view_annotator(data, container ) {
  this._ID = '_via_view_annotator_';
  this.d = data;
  this.c = container;
  this.file_annotator = [];
  this.view_mode = _VIA_VIEW_MODE.UNKNOWN;

  // constants
  this.GTIMELINE_ROW_DEFAULT_COUNT = '4';
  this.GTIMELINE_ROW_HEIGHT_MAP = { '1':21, '2':24, '3':28, '4':32, '5':37, '6':41, '7':45,'8':49,'9':53,'10':57,'12':65,'14':73,'16':80 };

  // zoom
  this.zoom_mode = _VIA_DEFAULT_ZOOM_MODE;
  this.zoom_scale_value_index = _VIA_ZOOM_SCALE_DEFAULT_INDEX;

  // state variables
  this.region_draw_shape = _VIA_RSHAPE.RECTANGLE;
  this.creg_label_aid = '1';

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );

  this.d.on_event('metadata_add', this._ID, this._on_event_metadata_add.bind(this));
  this.d.on_event('metadata_update', this._ID, this._on_event_metadata_update.bind(this));

  this._init();
}

_via_view_annotator.prototype._init = function() {
  //this._view_clear_all_file_annotator();
  this._show_start_info();

  if ( ! this.d.store.config.ui.hasOwnProperty('spatial_region_label_attribute_id') ) {
    this.d.store.config.ui['spatial_region_label_attribute_id'] = '';
  }
}

_via_view_annotator.prototype._magnifier_toggle = function() {
  for(var i = 0; i < this.file_annotator.length; ++i) {
    for(var j = 0; j < this.file_annotator[i].length; ++j) {
      this.file_annotator[i][j]._magnifier_toggle.bind(this.file_annotator[i][j])();
    }
  }
}

_via_view_annotator.prototype._show_start_info = function() {
  this.c.setAttribute('style', 'grid-template-rows:1fr;')
  var via_page = document.createElement('div');
  via_page.setAttribute('id', 'via_start_info');
  via_page.innerHTML = document.getElementById('via_start_info_content').innerHTML;
  this.c.innerHTML = '';
  this.c.appendChild(via_page);
}

_via_view_annotator.prototype.view_show = function(vid) {
  this._view_clear_all_file_annotator();
  this.vid = vid;
  this._view_init(vid);
  this.emit_event( 'view_show', { 'vid':this.vid } );
}

_via_view_annotator.prototype._view_init = function(vid) {
  var file_count = this.d.store.view[vid].fid_list.length;
  switch(file_count) {
  case 1:
    if ( this._view_has_only_image(vid) ) {
      this._view_annotate_single_image(vid);
    } else {
      if ( this._view_has_only_video(vid) ) {
        this._view_annotate_single_video(vid);
      } else {
        if ( this._view_has_only_audio(vid) ) {
          this._view_annotate_single_audio(vid);
        } else {
          console.warn('View mode not supported yet!');
        }
      }
    }
    break;

  case 2:
    if ( this._view_has_only_image(vid) ) {
      this._view_annotate_two_images(vid);
    } else {
      console.warn('View mode not supported yet!');
    }
    break;

  default:
    console.warn('View mode not supported yet!');
  }
}

_via_view_annotator.prototype._view_annotate_single_image = function(vid) {
  this.view_mode = _VIA_VIEW_MODE.IMAGE1;

  this.c.innerHTML = '';
  // for viewing content of a view and definition of metadata.{xy, z, av} for the view
  this.view_content_container = document.createElement('div');
  this.view_content_container.setAttribute('class', 'view_content_container');

  this.c.setAttribute('style', 'grid-template-rows:1fr;')
  this.c.appendChild(this.view_content_container);

  // occupy the full container with single image
  this.file_annotator = [];
  this.file_container = [];
  this.view_content_container.innerHTML = '';
  this._view_split_content_container(this.view_content_container, this.file_container, 1, 1);

  this.file_annotator[0] = [];
  var vid0 = this.d.view_get_file_vid( this.d.store.view[vid].fid_list[0] );
  this.file_annotator[0][0] = new _via_file_annotator(this, this.d, vid0, '', this.file_container[0][0]);
  this.file_annotator[0][0]._file_load().then( function(ok) {
    this.file_annotator[0][0]._rinput_enable();
  }.bind(this), function(err) {
    // handled by _via_file_annotator._file_load_show_error_page();
  }.bind(this));
}

_via_view_annotator.prototype._view_annotate_single_video = function(vid) {
  this.view_mode = _VIA_VIEW_MODE.VIDEO1;

  this.c.innerHTML = '';
  // for viewing content of a view and definition of metadata.{xy, z, av} for the view
  this.view_content_container = document.createElement('div');
  this.view_content_container.setAttribute('class', 'view_content_container');

  // for definition of metadata.{z, v} for a view
  this.view_metadata_container = document.createElement('div');
  this.view_metadata_container.setAttribute('class', 'view_metadata_container');

  if(this.d.store['config']['ui'].hasOwnProperty('gtimeline_container_height')) {
    // fix for pixel height stored by projects created using via-3.0.7
    var via307_fix = { '17':'1','20':'2','24':'3','28':'4','33':'5','37':'6','41':'7','45':'8','49':'9','53':'10','61':'12','69':'14','76':'16','85':'18' };
    if(this.d.store['config']['ui']['gtimeline_container_height'] in via307_fix) {
      this.d.store['config']['ui']['gtimeline_visible_row_count'] = via307_fix[ this.d.store['config']['ui']['gtimeline_container_height'] ];
    }
    delete this.d.store['config']['ui']['gtimeline_container_height'];
  }
  if( !this.d.store['config']['ui'].hasOwnProperty('gtimeline_visible_row_count')) {
    this.d.store['config']['ui']['gtimeline_visible_row_count'] = this.GTIMELINE_ROW_DEFAULT_COUNT;
  }

  var gtimeline_visible_row_count = this.d.store['config']['ui']['gtimeline_visible_row_count'];
  this.gtimeline_container_height = this.GTIMELINE_ROW_HEIGHT_MAP[gtimeline_visible_row_count];
  this.c.setAttribute('style', 'grid-template-rows:1fr ' + this.gtimeline_container_height + 'ch;')
  this.c.appendChild(this.view_content_container);
  this.c.appendChild(this.view_metadata_container);
  this.view_metadata_container.style.display = 'block';

  // occupy the full container with single image
  this.file_annotator = [];
  this.file_container = [];
  this.view_content_container.innerHTML = '';
  this.view_metadata_container.innerHTML = '';
  this._view_split_content_container(this.view_content_container, this.file_container, 1, 1);

  this.file_annotator[0] = [];
  var fid0 = this.d.store.view[vid].fid_list[0];
  var vid0 = this.d.view_get_file_vid( fid0 );
  this.file_annotator[0][0] = new _via_file_annotator(this, this.d, vid0, '', this.file_container[0][0]);

  this.file_annotator[0][0]._file_load().then( function(ok) {
    this.view_metadata_container.innerHTML = '';
    // setup view metadata editor
    this.temporal_segmenter_container = document.createElement('div');
    this.temporal_segmenter_container.classList.add('temporal_segmenter_container');
    this.view_metadata_container.appendChild(this.temporal_segmenter_container);
    this.temporal_segmenter = new _via_temporal_segmenter(this.file_annotator[0][0],
                                                          this.temporal_segmenter_container,
                                                          vid0,
                                                          this.d,
                                                          this.file_annotator[0][0].file_html_element
                                                         );
    this.temporal_segmenter.on_event('edit_frame_regions', this._ID, this.file_annotator[0][0]._on_event_edit_frame_regions.bind(this.file_annotator[0][0]));
  }.bind(this), function(err) {
    this.c.removeChild(this.view_metadata_container);
    this.c.setAttribute('style', 'grid-template-rows:1fr;')
  }.bind(this));
}

_via_view_annotator.prototype._view_annotate_single_audio = function(vid) {
  this.view_mode = _VIA_VIEW_MODE.AUDIO1;

  this.c.innerHTML = '';
  // for viewing content of a view and definition of metadata.{xy, z, av} for the view
  this.view_content_container = document.createElement('div');
  this.view_content_container.setAttribute('class', 'view_content_container');

  // for definition of metadata.{z, v} for a view
  this.view_metadata_container = document.createElement('div');
  this.view_metadata_container.setAttribute('class', 'view_metadata_container');

  if(this.d.store['config']['ui'].hasOwnProperty('gtimeline_container_height')) {
    // fix for pixel height stored by projects created using via-3.0.7
    var via307_fix = { '17':'1','20':'2','24':'3','28':'4','33':'5','37':'6','41':'7','45':'8','49':'9','53':'10','61':'12','69':'14','76':'16','85':'18' };
    if(this.d.store['config']['ui']['gtimeline_container_height'] in via307_fix) {
      this.d.store['config']['ui']['gtimeline_visible_row_count'] = via307_fix[ this.d.store['config']['ui']['gtimeline_container_height'] ];
    }
    delete this.d.store['config']['ui']['gtimeline_container_height'];
  }
  if( !this.d.store['config']['ui'].hasOwnProperty('gtimeline_visible_row_count')) {
    this.d.store['config']['ui']['gtimeline_visible_row_count'] = this.GTIMELINE_ROW_DEFAULT_COUNT;
  }

  var gtimeline_visible_row_count = this.d.store['config']['ui']['gtimeline_visible_row_count'];
  this.gtimeline_container_height = this.GTIMELINE_ROW_HEIGHT_MAP[gtimeline_visible_row_count];
  this.c.setAttribute('style', 'grid-template-rows:1fr ' + this.gtimeline_container_height + 'ch;')
  this.c.appendChild(this.view_content_container);
  this.c.appendChild(this.view_metadata_container);
  this.view_metadata_container.style.display = 'block';

  // occupy the full container with single image
  this.file_annotator = [];
  this.file_container = [];
  this.view_content_container.innerHTML = '';
  this.view_metadata_container.innerHTML = '';
  this._view_split_content_container(this.view_content_container, this.file_container, 1, 1);

  this.file_annotator[0] = [];
  var fid0 = this.d.store.view[vid].fid_list[0];
  var vid0 = this.d.view_get_file_vid( fid0 );
  this.file_annotator[0][0] = new _via_file_annotator(this, this.d, vid0, '', this.file_container[0][0]);

  this.file_annotator[0][0]._file_load().then( function(ok) {
    this.view_metadata_container.innerHTML = '';
    // setup view metadata editor
    this.temporal_segmenter_container = document.createElement('div');
    this.temporal_segmenter_container.classList.add('temporal_segmenter_container');
    this.view_metadata_container.appendChild(this.temporal_segmenter_container);
    this.temporal_segmenter = new _via_temporal_segmenter(this.file_annotator[0][0],
                                                          this.temporal_segmenter_container,
                                                          vid0,
                                                          this.d,
                                                          this.file_annotator[0][0].file_html_element
                                                         );
  }.bind(this), function(err) {
    _via_util_msg_show('Failed to load audio!', true);
  }.bind(this));
}

_via_view_annotator.prototype._view_annotate_two_images = function(vid) {
  this.view_mode = _VIA_VIEW_MODE.IMAGE2;

  this.c.innerHTML = '';
  // for viewing content of a view and definition of metadata.{xy, z, av} for the view
  this.view_content_container = document.createElement('div');
  this.view_content_container.setAttribute('class', 'view_content_container');

  // for definition of metadata.{z, v} for a view
  this.view_metadata_container = document.createElement('div');
  this.view_metadata_container.setAttribute('class', 'view_metadata_container');

  this.c.setAttribute('style', 'grid-template-rows:1fr auto;')
  this.c.appendChild(this.view_content_container);
  this.c.appendChild(this.view_metadata_container);
  this.view_metadata_container.style.display = 'block';

  this.file_annotator = [];
  this.file_container = [];
  this.view_content_container.innerHTML = '';
  this._view_split_content_container(this.view_content_container, this.file_container, 1, 2);

  this.file_annotator[0] = [];
  var vid0 = this.d.view_get_file_vid( this.d.store.view[vid].fid_list[0] );
  var vid1 = this.d.view_get_file_vid( this.d.store.view[vid].fid_list[1] );
  this.file_annotator[0][0] = new _via_file_annotator(this, this.d, vid0, 'Image 1', this.file_container[0][0]);
  this.file_annotator[0][1] = new _via_file_annotator(this, this.d, vid1, 'Image 2', this.file_container[0][1]);

  // first setup view metadata editor
  this.img_pair_annotator_container = document.createElement('div');
  this.img_pair_annotator_container.classList.add('img_pair_annotator_container');
  this.view_metadata_container.innerHTML = '';
  this.view_metadata_container.appendChild(this.img_pair_annotator_container);
  this._metadata_update();

  // then load files
  var promise_list = [];
  promise_list.push( this.file_annotator[0][0]._file_load() );
  promise_list.push( this.file_annotator[0][1]._file_load() );
  Promise.all( promise_list ).then( function(ok) {
  }.bind(this), function(err) {
    console.warn('Failed to load images');
    _via_util_msg_show('Failed to load images!');
  }.bind(this));

}

_via_view_annotator.prototype._view_has_only_image = function(vid) {
  var fid;
  for ( var vfindex in this.d.store.view[vid].fid_list ) {
    fid = this.d.store.view[vid].fid_list[vfindex];
    if ( this.d.store.file[fid].type !== _VIA_FILE_TYPE.IMAGE ) {
      return false;
    }
  }
  return true;
}

_via_view_annotator.prototype._view_has_only_video = function(vid) {
  var fid;
  for ( var vfindex in this.d.store.view[vid].fid_list ) {
    fid = this.d.store.view[vid].fid_list[vfindex];
    if ( this.d.store.file[fid].type !== _VIA_FILE_TYPE.VIDEO ) {
      return false;
    }
  }
  return true;
}

_via_view_annotator.prototype._view_has_only_audio = function(vid) {
  var fid;
  for ( var vfindex in this.d.store.view[vid].fid_list ) {
    fid = this.d.store.view[vid].fid_list[vfindex];
    if ( this.d.store.file[fid].type !== _VIA_FILE_TYPE.AUDIO ) {
      return false;
    }
  }
  return true;
}

//
// view container
//
_via_view_annotator.prototype._view_split_content_container = function(container, file_container, nrow, ncol) {
  for ( var i = 0; i < nrow; ++i ) {
    file_container[i] = [];
    for ( var j = 0; j < ncol; ++j ) {
      file_container[i][j] = document.createElement('div');
      file_container[i][j].setAttribute('class', 'file_container');
      file_container[i][j].setAttribute('data-i', i);
      file_container[i][j].setAttribute('data-j', j);
      container.appendChild(file_container[i][j]);
    }
  }
  container.setAttribute('style',
                         'grid-template-columns:repeat(' + ncol + ',1fr);' +
                         'grid-template-rows:repeat(' + nrow + ',1fr);' +
                         'grid-gap:0.5em;');
}

//
// misc
//
_via_view_annotator.prototype.set_region_draw_shape = function(shape) {
  if ( _VIA_RSHAPE.hasOwnProperty(shape) ) {
    this.region_draw_shape = _VIA_RSHAPE[shape];
    switch(this.region_draw_shape) {
    case _VIA_RSHAPE.POINT:
      _via_util_msg_show('Click to define feature points');
      break;
    case _VIA_RSHAPE.RECTANGLE:
      _via_util_msg_show('Click and drag mouse cursor to define a rectangular region');
      break;
    case _VIA_RSHAPE.EXTREME_RECTANGLE:
      _via_util_msg_show('Click and define the left, top, right and bottom (in any order) extreme points of a rectangular object to define its bounding box.');
      break;
    case _VIA_RSHAPE.CIRCLE:
      _via_util_msg_show('Click and drag mouse cursor to define a circular region');
      break;
    case _VIA_RSHAPE.EXTREME_CIRCLE:
      _via_util_msg_show('Click and define three points on the circumference to define a circular region.');
      break;
    case _VIA_RSHAPE.ELLIPSE:
      _via_util_msg_show('Click and drag mouse cursor to define a elliptical region');
      break;
    case _VIA_RSHAPE.LINE:
      _via_util_msg_show('Click and drag mouse cursor to define a line region');
      break;
    case _VIA_RSHAPE.POLYLINE:
      _via_util_msg_show('Click to define vertices of polyline and to finish click at the last vertex.');
      break;
    case _VIA_RSHAPE.POLYGON:
      _via_util_msg_show('Click to define vertices of polygon and to finish click at the last vertex.');
      break;
    }
  }
}

//
// cleanup
//
_via_view_annotator.prototype._view_clear_all_file_annotator = function() {
  // _via_file_annotator are attached as events listeners in _via_data
  // we must also remove these events listeners
  // cleanup resources acquired by each of this.file_annotator[i][j]
  for ( var i = 0; i < this.file_annotator.length; ++i ) {
    for ( var j = 0; j < this.file_annotator[i].length; ++j ) {
      this.d.clear_events(this.file_annotator[i][j]._ID );
      delete this.file_annotator[i][j];
    }
  }
}

//
// view metadata editor
//
_via_view_annotator.prototype._metadata_update = function() {
  var anchor_id = 'FILEN_Z0_XY0';
  var table = document.createElement('table');
  // this view has no metadata
  for ( var aindex in this.d.cache.attribute_group[anchor_id] ) {
    var mid = '';
    var aid = this.d.cache.attribute_group[anchor_id][aindex];
    if ( this.d.cache.mid_list.hasOwnProperty(this.vid) ) {
      mid = this.d.cache.mid_list[this.vid][0];
    }
    table.appendChild( this._metadata_table_row(aid, mid) );
  }

  this.img_pair_annotator_container.innerHTML = '';
  this.img_pair_annotator_container.appendChild(table);
}

_via_view_annotator.prototype._metadata_table_row = function(aid, mid) {
  var tr = document.createElement('tr');

  var adesc_col = document.createElement('td');
  adesc_col.innerHTML = this.d.store.attribute[aid].desc;
  tr.appendChild(adesc_col);

  var aopt_col = document.createElement('td');
  aopt_col.appendChild(this._attribute_html_element(aid, this._metadata_on_update.bind(this), mid));
  tr.appendChild(aopt_col);
  return tr;
}

_via_view_annotator.prototype._metadata_on_update = function(e) {
  var aid = e.target.dataset.aid;
  var mid = e.target.dataset.mid;

  var avalue;
  switch(this.d.store.attribute[aid].type) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    avalue = e.target.value;
    break;
  default:
    avalue = e.target.dataset.oid;
  }

  if ( mid ) {
    var mid = e.target.dataset.mid;
    // update existing metadata
    this.d.metadata_update_av(this.vid, mid, aid, avalue).then( function(ok) {
      _via_util_msg_show('Update metadata');
    }.bind(this), function(err) {
      _via_util_msg_show('Failed to update metadata!');
    }.bind(this));
  } else {
    // add new metadata
    var av = {};
    av[aid] = avalue;
    this.d.metadata_add(this.vid, [], [], av).then( function(ok) {
      _via_util_msg_show('Created metadata');
      this._metadata_update();
    }.bind(this), function(err) {
      _via_util_msg_show('Failed to update metadata!');
    }.bind(this));
  }

}

//
// attribute helper methods
//
_via_view_annotator.prototype._attribute_html_element = function(aid, onchange_handler, mid) {
  var el;
  switch(this.d.store.attribute[aid].type) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    el = document.createElement('textarea');
    if ( mid !== '') {
      el.setAttribute('data-mid', mid);
      if(this.d.store.metadata[mid].av.hasOwnProperty(aid)) {
        el.innerHTML = this.d.store.metadata[mid].av[aid];
      }
    }
    el.setAttribute('data-aid', aid);
    el.addEventListener('change', onchange_handler);
    break;

  case _VIA_ATTRIBUTE_TYPE.SELECT:
    el = document.createElement('select');
    var selected_oid = '';
    if ( mid!== '' &&
         this.d.store.metadata[mid].av.hasOwnProperty(aid)
       ) {
      selected_oid = this.d.store.metadata[mid].av[aid];
    }

    var oid;
    for ( oid in this.d.store.attribute[aid].options ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', oid);
      oi.setAttribute('data-oid', oid);
      oi.setAttribute('data-aid', aid);
      if ( mid !== '' ) {
        oi.setAttribute('data-mid', mid);
      }
      if ( selected_oid === oid ) {
        oi.setAttribute('selected', 'true');
      }
      oi.setAttribute('name', this.d.store.attribute[aid].options[oid]);
      oi.addEventListener('change', onchange_handler);
      el.appendChild(oi);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.RADIO:
    el = document.createElement('div');
    var selected_oid = '';
    if ( mid!== '' &&
         this.d.store.metadata[mid].av.hasOwnProperty(aid)
       ) {
      selected_oid = this.d.store.metadata[mid].av[aid];
    }

    var oid;
    for ( oid in this.d.store.attribute[aid].options ) {
      var container = document.createElement('span');
      container.setAttribute('class', 'nobreak');
      var inp = document.createElement('input');
      inp.setAttribute('type', 'radio');
      inp.setAttribute('value', oid);
      inp.setAttribute('data-oid', oid);
      inp.setAttribute('data-aid', aid);
      if ( mid !== '' ) {
        inp.setAttribute('data-mid', mid);
      }

      inp.setAttribute('name', this.d.store.attribute[aid].aname);
      inp.addEventListener('change', onchange_handler);
      if ( selected_oid === oid ) {
        inp.checked = true;
      } else {
        inp.checked = false;
      }

      var label = document.createElement('label');
      label.setAttribute('for', oid);
      label.innerHTML = this.d.store.attribute[aid].options[oid];
      container.appendChild(inp);
      container.appendChild(label);
      el.appendChild(container);
    }
    break;

  default:
    console.warn('undefined attribute type = ' + this.type);
  }
  return el;
}

//
// keyboard handler
//
_via_view_annotator.prototype._on_event_keydown = function(e) {
  if ( this.view_mode === _VIA_VIEW_MODE.VIDEO1 ) {
    this.file_annotator[0][0]._rinput_keydown_handler(e);
    if ( this.file_annotator[0][0].selected_mid_list.length === 0 ) {
      // no spatial region is selected
      // therefore, the temporal segmenter keyhandler should further handle this event
      if ( this.temporal_segmenter ) {
        this.temporal_segmenter._on_event_keydown(e);
      }
    }
    return;
  }

  if ( this.view_mode === _VIA_VIEW_MODE.AUDIO1 ) {
    this.file_annotator[0][0]._rinput_keydown_handler(e);
    if ( this.temporal_segmenter ) {
      this.temporal_segmenter._on_event_keydown(e);
    }
    return;
  }

  if ( this.view_mode === _VIA_VIEW_MODE.IMAGE1 ||
       this.view_mode === _VIA_VIEW_MODE.IMAGE2
     ) {
    if ( this.view_mode === _VIA_VIEW_MODE.IMAGE1 ) {
      this.file_annotator[0][0]._rinput_keydown_handler(e);
    } else {
      if ( e.key === 'ArrowRight' || e.key === 'ArrowLeft' ) {
        if(e.key === 'ArrowRight') {
          this.emit_event('view_next', {});
        } else {
          this.emit_event('view_prev', {});
        }
      }
    }
    return;
  }
}

//
// external events
//
_via_view_annotator.prototype._on_event_metadata_add = function(data, event_payload) {
  var vid = event_payload.vid;
  var mid = event_payload.mid;

  if ( typeof(this.temporal_segmenter) !== 'undefined' ) {
    this.temporal_segmenter._on_event_metadata_add(vid, mid);
  }
}

_via_view_annotator.prototype._on_event_metadata_update = function(data, event_payload) {
  var vid = event_payload.vid;
  var mid = event_payload.mid;

  if ( typeof(this.temporal_segmenter) !== 'undefined' ) {
    this.temporal_segmenter._on_event_metadata_update(vid, mid);
  }
}
</script>
<!-- END: Contents of file: js/_via_view_annotator.js-->

<!-- START: Contents of file: js/_via_view_manager.js-->
<script>
/**
 *
 * @class
 * @classdesc View manager
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 5 Apr. 2019
 *
 */

'use strict';

function _via_view_manager(data, view_annotator, container) {
  this._ID = '_via_view_manager_';
  this.d = data;
  this.va = view_annotator;
  this.c = container;

  this.view_selector_vid_list = [];
  var is_view_filtered_by_regex = false;

  // registers on_event(), emit_event(), ... methods from
  // _via_event to let this module listen and emit events
  _via_event.call( this );

  this.d.on_event('project_loaded', this._ID, this._on_event_project_loaded.bind(this));
  this.d.on_event('project_updated', this._ID, this._on_event_project_updated.bind(this));
  this.d.on_event('view_bulk_add', this._ID, this._on_event_view_bulk_add.bind(this));
  this.d.on_event('view_del', this._ID, this._on_event_view_del.bind(this));
  this.va.on_event('view_show', this._ID, this._on_event_view_show.bind(this));
  this.va.on_event('view_next', this._ID, this._on_event_view_next.bind(this));
  this.va.on_event('view_prev', this._ID, this._on_event_view_prev.bind(this));

  this._init_ui_elements();
}

_via_view_manager.prototype._init = function() {
  this._init_ui_elements();
  this._view_selector_update();
}

_via_view_manager.prototype._init_ui_elements = function() {
  this.pname = document.createElement('input');
  this.pname.setAttribute('type', 'text');
  this.pname.setAttribute('id', 'via_project_name_input');
  this.pname.setAttribute('value', this.d.store.project.pname);
  this.pname.setAttribute('title', 'Project Name (click to update)');
  this.pname.addEventListener('change', this._on_pname_change.bind(this));

  this.view_selector = document.createElement('select');
  this.view_selector.setAttribute('class', 'view_selector');
  this.view_selector.setAttribute('title', 'Select a file for annotation');
  this.view_selector.addEventListener('change', this._on_view_selector_change.bind(this));

  this.view_filter_regex = document.createElement('input');
  this.view_filter_regex.setAttribute('type', 'text');
  this.view_filter_regex.setAttribute('class', 'view_filter_regex');
  this.view_filter_regex.setAttribute('title', 'Filter file list');
  this.view_filter_regex.setAttribute('placeholder', 'Search');
  this.view_filter_regex.addEventListener('input', this._on_view_filter_regex_change.bind(this));

  this.c.innerHTML = '';
  this.c.appendChild(this.pname);
  this.c.appendChild(this.view_selector);
  this.c.appendChild(this.view_filter_regex);
}

//
// UI elements change listeners
//
_via_view_manager.prototype._on_pname_change = function(e) {
  this.d.store.project.pname = e.target.value.trim();
}

_via_view_manager.prototype._on_view_selector_change = function(e) {
  var vid = e.target.options[e.target.selectedIndex].value;
  if ( vid !== this.va.vid ) {
    this.va.view_show(vid);
  }
}

_via_view_manager.prototype._on_next_view = function() {
  if ( this.view_selector.options.length ) {
    var vid = this.view_selector.options[this.view_selector.selectedIndex].value;
    var vindex = this.view_selector_vid_list.indexOf(vid);
    if ( vindex !== -1 ) {
      var next_vindex = vindex + 1;
      if ( next_vindex >= this.view_selector_vid_list.length ) {
        next_vindex = 0;
      }
      this.va.view_show( this.view_selector_vid_list[next_vindex] );
    } else {
      _via_util_msg_show('Cannot move to next view!');
    }
  }
}

_via_view_manager.prototype._on_prev_view = function() {
  if ( this.view_selector.options.length ) {
    var vid = this.view_selector.options[this.view_selector.selectedIndex].value;
    var vindex = this.view_selector_vid_list.indexOf(vid);
    if ( vindex !== -1 ) {
      var prev_vindex = vindex - 1;
      if ( prev_vindex < 0 ) {
        prev_vindex = this.view_selector_vid_list.length - 1;
      }
      this.va.view_show( this.view_selector_vid_list[prev_vindex] );
    } else {
      _via_util_msg_show('Cannot move to next view!');
    }
  }
}

_via_view_manager.prototype._on_event_view_show = function(data, event_payload) {
  var vid = event_payload.vid.toString();
  this.view_selector.selectedIndex = -1;

  // ensure that the view selector shows the view being displayed
  var n = this.view_selector.options.length;
  for ( var i = 0; i < n; ++i ) {
    if ( this.view_selector.options[i].value === vid ) {
      this.view_selector.selectedIndex = i;
      break;
    }
  }
}

_via_view_manager.prototype._on_event_view_next = function(data, event_payload) {
  this._on_next_view();
}

_via_view_manager.prototype._on_event_view_prev = function(data, event_payload) {
  this._on_prev_view();
}

_via_view_manager.prototype._on_event_project_loaded = function(data, event_payload) {
  this._init_ui_elements();
  this._view_selector_update();
  if ( this.d.store.project.vid_list.length ) {
    // show first view by default
    this.va.view_show( this.d.store.project.vid_list[0] );
  }
}

_via_view_manager.prototype._on_event_project_updated = function(data, event_payload) {
  var current_vid = this.va.vid;
  this._init_ui_elements();
  this._view_selector_update();
  if ( this.d.store.project.vid_list.length ) {
    if ( current_vid in this.d.store.project.vid_list ) {
      this.va.view_show( current_vid );
    } else {
      // show first view by default
      this.va.view_show( this.d.store.project.vid_list[0] );
    }
  }
}

//
// View Selector
//
_via_view_manager.prototype._view_selector_clear = function() {
  this.view_selector.innerHTML = '';
  this.view_selector_vid_list = [];
}

_via_view_manager.prototype._view_selector_option_html = function(vindex, vid) {
  var oi = document.createElement('option');
  oi.setAttribute('value', vid);

  var file_count = this.d.store.view[vid].fid_list.length;
  var view_name;
  if ( file_count === 1 ) {
    var fid = this.d.store.view[vid].fid_list[0];
    view_name = this.d.store.file[fid].fname;
    oi.innerHTML = '[' + (parseInt(vindex)+1) + '] ' + decodeURI(view_name);
  } else {
    var filelist = [];
    var fid;
    for ( var findex in this.d.store.view[vid].fid_list ) {
      fid = this.d.store.view[vid].fid_list[findex];
      filelist.push(this.d.store.file[fid].fname);
    }
    oi.innerHTML = '[' + (parseInt(vindex)+1) + '] ' + filelist.join(', ');
  }
  return oi;
}

_via_view_manager.prototype._view_selector_update = function() {
  if ( this.is_view_filtered_by_regex ) {
    this._view_selector_update_regex();
  } else {
    this._view_selector_update_showall();
  }
}

_via_view_manager.prototype._view_selector_update_regex = function(regex) {
  if ( regex === '' ||
       typeof(regex) === 'undefined'
     ) {
    this._view_selector_update_showall();
  } else {
    var existing_vid = '';
    if ( this.view_selector.options.length ) {
      if ( this.view_selector.selectedIndex !== -1 ) {
        existing_vid = this.view_selector.options[this.view_selector.selectedIndex].value;
      }
    }
    this._view_selector_clear();
    var vid, fid;
    for ( var vindex in this.d.store.project.vid_list ) {
      vid = this.d.store.project.vid_list[vindex];
      for ( var findex in this.d.store.view[vid].fid_list ) {
        fid = this.d.store.view[vid].fid_list[findex];
        if ( this.d.store.file[fid].fname.match(regex) !== null ) {
          this.view_selector.appendChild( this._view_selector_option_html(vindex, vid) );
          this.view_selector_vid_list.push(vid);
          break;
        }
      }
    }
    this.is_view_selector_regex_active = true;
    var existing_vid_index = this.view_selector_vid_list.indexOf(existing_vid);
    if ( existing_vid_index === -1 ) {
      if ( this.view_selector_vid_list.length ) {
        this.va.view_show( this.view_selector_vid_list[0] );
      }
    } else {
      this.view_selector.selectedIndex = existing_vid_index;
    }
  }
}

_via_view_manager.prototype._view_selector_update_showall = function() {
  var existing_selectedIndex = this.view_selector.selectedIndex;
  var existing_vid;
  if ( existing_selectedIndex !== -1 ) {
    existing_vid = this.view_selector.options[existing_selectedIndex].value;
  }
  this._view_selector_clear();

  var vid;
  for ( var vindex in this.d.store.project.vid_list ) {
    vid = this.d.store.project.vid_list[vindex];
    this.view_selector.appendChild( this._view_selector_option_html(vindex, vid) );
    this.view_selector_vid_list.push(vid);
  }
  this.is_view_filtered_by_regex = false;
  if ( existing_selectedIndex !== -1 ) {
    var existing_vid_index = this.view_selector_vid_list.indexOf(existing_vid);
    if ( existing_vid_index === -1 ) {
      this.view_selector.selectedIndex = -1;
    } else {
      this.view_selector.selectedIndex = existing_vid_index;
    }
  }
}

_via_view_manager.prototype._on_view_filter_regex_change = function() {
  var regex = this.view_filter_regex.value;
  this._view_selector_update_regex(regex);
}

_via_view_manager.prototype._file_add_from_filelist = function(filelist) {
  this.d.view_bulk_add_from_filelist(filelist).then( function(ok) {
    var filetype_summary = {};
    var fid, ftype_str;
    for ( var findex in ok.fid_list ) {
      fid = ok.fid_list[findex];
      ftype_str = _via_util_file_type_to_str( this.d.store.file[fid].type );
      if ( ! filetype_summary.hasOwnProperty(ftype_str) ) {
        filetype_summary[ftype_str] = 0;
      }
      filetype_summary[ftype_str] = filetype_summary[ftype_str] + 1;
    }
    _via_util_msg_show('Added ' + ok.fid_list.length + ' files. ' + JSON.stringify(filetype_summary));
  }.bind(this), function(err) {
    _via_util_msg_show('Failed to add files! [' + err + ']');
    console.warn(err);
  }.bind(this));
}

_via_view_manager.prototype._on_add_media_local = function() {
  _via_util_file_select_local(_VIA_FILE_SELECT_TYPE.IMAGE | _VIA_FILE_SELECT_TYPE.VIDEO | _VIA_FILE_SELECT_TYPE.AUDIO,
                              this._file_add_local.bind(this),
                              true);
}

_via_view_manager.prototype._file_add_local = function(e) {
  var files = e.target.files;
  var filelist = [];
  for ( var findex = 0; findex < files.length; ++findex ) {
    filelist.push({ 'fname':files[findex].name,
                    'type':_via_util_infer_file_type_from_filename(files[findex].name),
                    'loc':_VIA_FILE_LOC.LOCAL,
                    'src':files[findex],
                  });
  }
  this._file_add_from_filelist(filelist);
}

_via_view_manager.prototype._on_event_view_bulk_add = function(data, event_payload) {
  this._view_selector_update();
  this.d._cache_update();
  if ( event_payload.vid_list.length ) {
    this.va.view_show( event_payload.vid_list[0] );
  }
}

_via_view_manager.prototype._on_add_media_remote = function() {
  var url = window.prompt('Enter URL of an image, audio or video (e.g. http://www....)',
                          '');
  var filelist = [ {'fname':url,
                    'type':_via_util_infer_file_type_from_filename(url),
                    'loc':_VIA_FILE_LOC.URIHTTP,
                    'src':url,
                   }
                 ];

  this._file_add_from_filelist(filelist);
}

_via_view_manager.prototype._on_add_media_bulk = function() {
  _via_util_file_select_local(_VIA_FILE_SELECT_TYPE.TEXT,
                              this._on_add_media_bulk_file_selected.bind(this), false);
}

_via_view_manager.prototype._on_add_media_bulk_file_selected = function(e) {
  if ( e.target.files.length ) {
    _via_util_load_text_file(e.target.files[0], this._on_add_media_bulk_file_load.bind(this));
  }
}

_via_view_manager.prototype._on_add_media_bulk_file_load = function(file_data) {
  var url_list = file_data.split('\n');
  if ( url_list.length ) {
    var filelist = [];
    for ( var i = 0; i < url_list.length; ++i ) {
      if ( url_list[i] === '' ||
           url_list[i] === ' ' ||
           url_list[i] === '\n'
         ) {
        continue; // skip
      }
      filelist.push({ 'fname':url_list[i],
                      'type':_via_util_infer_file_type_from_filename(url_list[i]),
                      'loc':_via_util_infer_file_loc_from_filename(url_list[i]),
                      'src':url_list[i],
                    });
    }
    this._file_add_from_filelist(filelist);
  }
}

_via_view_manager.prototype._on_del_view = function() {
  this.d.view_del(this.va.vid).then( function(ok) {
    _via_util_msg_show('Deleted view ' + ( parseInt(ok.vindex) + 1));
  }.bind(this), function(err) {
    console.warn(err);
  }.bind(this));
}

_via_view_manager.prototype._on_event_view_del = function(data, event_payload) {
  this._view_selector_update();
  var vindex = event_payload.vindex;
  if ( this.d.store.project.vid_list.length ) {
    if ( vindex < this.d.store.project.vid_list.length ) {
      this.va.view_show( this.d.store.project.vid_list[vindex] );
    } else {
      this.va.view_show( this.d.store.project.vid_list[ this.d.store.project.vid_list.length - 1 ] );
    }
  } else {
    this.va._init();
  }
}
</script>
<!-- END: Contents of file: js/_via_view_manager.js-->

<!-- START: Contents of file: js/_via_editor.js-->
<script>
/**
 * @class
 * @classdesc Editor for metadata and attributes
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @since 14 Jan. 2019
 */
function _via_editor(data, view_annotator, container) {
  this._ID = '_via_editor';
  this.d  = data;
  this.va = view_annotator;
  this.c  = container;

  // initialise event listeners
  this.d.on_event('file_show', this._ID, this.on_event_file_show.bind(this));
  this.d.on_event('metadata_add', this._ID, this.on_event_metadata_add.bind(this));
  this.d.on_event('metadata_del', this._ID, this.on_event_metadata_del.bind(this));
  this.d.on_event('attribute_update', this._ID, this.on_event_attribute_update.bind(this));
  this.d.on_event('attribute_del', this._ID, this.on_event_attribute_del.bind(this));
  this.d.on_event('attribute_add', this._ID, this.on_event_attribute_add.bind(this));
}

_via_editor.prototype.TYPE = { 'METADATA':2, 'ATTRIBUTE':3 };


_via_editor.prototype.toggle = function() {
  if ( this.c.classList.contains('hide') ) {
    this.show();
  } else {
    this.hide();
  }
}

_via_editor.prototype.hide = function() {
  this.c.innerHTML = '';
  this.c.classList.add('hide');
}
_via_editor.prototype.show = function() {
  this.c.classList.remove('hide');

  // top line: editor content selector {metadata, attribute}
  this.editor_content_selector = document.createElement('div');
  this.editor_content_selector.setAttribute('class', 'editor_content_selector');
  var title = document.createElement('span');
  title.innerHTML = 'Show: ';
  this.edit_metadata_checkbox = document.createElement('input');
  this.edit_metadata_checkbox.setAttribute('name', 'metadata');
  this.edit_metadata_checkbox.setAttribute('type', 'checkbox');
  this.edit_metadata_checkbox.addEventListener('change', this.on_editor_content_select.bind(this));
  var edit_metadata_label = document.createElement('label');
  edit_metadata_label.innerHTML = 'Metadata';
  edit_metadata_label.setAttribute('for', 'metadata');
  edit_metadata_label.setAttribute('title', 'Metadata corresponds to values assigned by user for the attributes.');

  this.edit_attribute_checkbox = document.createElement('input');
  this.edit_attribute_checkbox.setAttribute('name', 'attribute');
  this.edit_attribute_checkbox.setAttribute('type', 'checkbox');
  this.edit_attribute_checkbox.addEventListener('change', this.on_editor_content_select.bind(this));
  var edit_attribute_label = document.createElement('label');
  edit_attribute_label.innerHTML = 'Attribute';
  edit_attribute_label.setAttribute('for', 'attribute');
  edit_attribute_label.setAttribute('title', 'Attribute corresponds to fields like name, color, etc. required to describe the region of interest');

  this.editor_content_selector.appendChild(title);
  this.editor_content_selector.appendChild(this.edit_attribute_checkbox);
  this.editor_content_selector.appendChild(edit_attribute_label);
  this.editor_content_selector.appendChild(this.edit_metadata_checkbox);
  this.editor_content_selector.appendChild(edit_metadata_label);

  // toolbar
  var toolbar = document.createElement('div');
  toolbar.setAttribute('class', 'toolbar');
  var close_button = document.createElement('button');
  close_button.setAttribute('class', 'text_button');
  close_button.innerHTML = '&times;';
  close_button.addEventListener('click', this.toggle.bind(this));
  toolbar.appendChild(close_button);

  // metadata
  this.metadata_container = document.createElement('div');
  this.metadata_container.setAttribute('id', 'metadata_container');
  this.metadata_container.setAttribute('class', 'metadata_container');
  this.metadata_view = document.createElement('table');
  var metadata_title = document.createElement('h2');
  metadata_title.innerHTML = 'Metadata';
  this.metadata_container.appendChild( metadata_title );
  this.metadata_container.appendChild( this.metadata_view );

  // attribute
  this.attribute_container = document.createElement('div');
  this.attribute_container.setAttribute('id', 'attribute_container');
  this.attribute_container.setAttribute('class', 'attribute_container');
  this.attribute_view = document.createElement('table');
  var attribute_title = document.createElement('h2');
  attribute_title.innerHTML = 'Attributes';

  this.attribute_container.appendChild( attribute_title );
  this.attribute_container.appendChild( this.get_attribute_name_entry_panel() );
  this.attribute_container.appendChild( this.attribute_view );

  this.content_container = document.createElement('div');
  this.content_container.setAttribute('class', 'content_container');
  this.content_container.appendChild(this.attribute_container);
  this.content_container.appendChild(this.metadata_container);

  // add everything to container
  this.c.appendChild(toolbar);
  //this.c.appendChild(this.editor_content_selector);
  this.c.appendChild(this.content_container);

  this.attributes_update();
  //this.metadata_update();

  // initial state of content
  this.edit_metadata_checkbox.checked = true;
  this.metadata_container.classList.add('hide');
  this.edit_attribute_checkbox.checked = true;
  this.attribute_container.classList.remove('hide');
}

//
// Editor content selector
//
_via_editor.prototype.on_editor_content_select = function(selector) {
  var element;
  switch(selector.target.name) {
  case 'metadata':
    element = this.metadata_container;
    break;
  case 'attribute':
    element = this.attribute_container;
    break;
  }

  if ( selector.target.checked ) {
    element.classList.remove('hide');
  } else {
    element.classList.add('hide');
  }
}

//
// metadata
//
_via_editor.prototype.metadata_clear = function() {
  this.metadata_view.innerHTML = '';
}

_via_editor.prototype.metadata_update = function() {
  this.metadata_clear();

  if ( this.va.vid ) {
    // fetch all metadata associated with this.va.vid
    this.d._cache_update_mid_list();
    var metadata_count = this.d.cache.mid_list[this.va.vid].length;
    console.log(metadata_count)
    if ( metadata_count ) {
      // add header
      this.metadata_view.appendChild( this.get_metadata_header() );

      // add each metadata
      var tbody = document.createElement('tbody');
      var metadata_index = 1;
      var mid;
      for ( var mindex in this.d.cache.mid_list[this.va.vid] ) {
        mid = this.d.cache.mid_list[this.va.vid][mindex];
        tbody.appendChild( this.metadata_get(mid, metadata_index) );
        metadata_index = metadata_index + 1;
      }
      this.metadata_view.appendChild(tbody);
    } else {
      this.metadata_view.innerHTML = '<tr><td>No metadata added yet!</td></tr>';
    }
  } else {
    this.metadata_view.innerHTML = '<tr><td><i>No metadata added yet!</i></td></tr>';
  }
}

_via_editor.prototype.metadata_get = function(mid, metadata_index) {
  var tr = document.createElement('tr');
  tr.setAttribute('data-mid', mid);

  // column: the action tools for metadata (like delete)
  var action_tools_container = document.createElement('td');
  this.get_metadata_action_tools(action_tools_container, mid);
  tr.appendChild( action_tools_container );

  // column: index of this metadata
  var metadata_index_container = document.createElement('td');
  metadata_index_container.innerHTML = metadata_index;
  metadata_index_container.setAttribute('title', mid);
  tr.appendChild(metadata_index_container);

  // column: z (temporal coordinate)
  var tcoordinate = document.createElement('td');
  tcoordinate.innerHTML = this.d.store.metadata[mid].z.join(', ');
  tr.appendChild(tcoordinate);

  // column: xy (spatial coordinate)
  var scoordinate = document.createElement('td');
  scoordinate.innerHTML = this.d.store.metadata[mid].xy.join(', ');
  tr.appendChild(scoordinate);

  // subsequent columns: what (i.e. the attributes for this metadata)
  for ( var aid in this.d.store.attribute ) {
    var td = document.createElement('td');
    td.appendChild( this.get_attribute_html_element(mid, aid) );
    tr.appendChild(td);
  }

  return tr;
}

_via_editor.prototype.get_metadata_action_tools = function(container, mid) {
  var del = _via_util_get_svg_button('micon_delete', 'Delete Metadata');
  del.setAttribute('data-mid', mid);
  del.addEventListener('click', this.metadata_del.bind(this));
  container.appendChild(del);

  /*
  var edit = _via_util_get_svg_button('micon_edit', 'Select Metadata for Editing');
  edit.setAttribute('data-fid', fid);
  edit.setAttribute('data-mid', mid);
  edit.addEventListener('click', this.metadata_edit.bind(this));
  container.appendChild(edit);
  */
}

_via_editor.prototype.get_metadata_header = function() {
  var tr = document.createElement('tr');
  tr.appendChild( this.html_element('th', '') );
  tr.appendChild( this.html_element('th', '#') );
  tr.appendChild( this.html_element('th', 'Temporal Coordinate') );
  tr.appendChild( this.html_element('th', 'Spatial Coordinate') );

  for ( var aid in this.d.store.attribute ) {
    tr.appendChild( this.html_element('th',
                                      this.d.store.attribute[aid].aname)
                  );
  }

  var thead = document.createElement('thead');
  thead.appendChild(tr);
  return thead;
}

_via_editor.prototype.html_element = function(name, text) {
  var e = document.createElement(name);
  e.innerHTML = text;
  return e;
}

//
// Metadata preview
//

_via_editor.prototype.jump_to_metadata = function(e) {
  var fid = e.target.dataset.fid;
  var mid = e.target.dataset.mid;
  var where_index = e.target.dataset.where_index;
  if ( this.d.metadata_store[fid][mid].where_target() === _VIA_WHERE_TARGET.SEGMENT &&
       this.d.metadata_store[fid][mid].where_target() === _VIA_WHERE_SHAPE.TIME
     ) {
    this.a.preload[fid].media_annotator.media.currentTime = this.d.metadata_store[fid][mid].where[where_index];
  }
}

//
// attribute
//
_via_editor.prototype.attribute_clear = function() {
  this.attribute_view.innerHTML = '';
}

_via_editor.prototype.attributes_update = function() {
  if ( this.c.classList.contains('hide') ) {
    return;
  }

  this.attribute_clear();

  if ( Object.keys(this.d.store.attribute).length ) {
    this.attribute_view.appendChild( this.get_attribute_header() );

    // add each metadata
    var tbody = document.createElement('tbody');
    for ( var aid in this.d.store.attribute ) {
      tbody.appendChild( this.get_attribute(aid) );
    }
    this.attribute_view.appendChild(tbody);
  }
}

_via_editor.prototype.get_attribute_header = function() {
  var tr = document.createElement('tr');
  tr.appendChild( this.html_element('th', '') );
  tr.appendChild( this.html_element('th', 'Id') );
  tr.appendChild( this.html_element('th', 'Name') );
  tr.appendChild( this.html_element('th', 'Anchor') );
  tr.appendChild( this.html_element('th', 'Input Type') );
  tr.appendChild( this.html_element('th', 'Description') );
  tr.appendChild( this.html_element('th', 'Options') );
  tr.appendChild( this.html_element('th', 'Default Value') );
  tr.appendChild( this.html_element('th', 'Preview') );

  var thead = document.createElement('thead');
  thead.appendChild(tr);
  return thead;
}

_via_editor.prototype.get_attribute = function(aid) {
  var tr = document.createElement('tr');
  tr.setAttribute('data-aid', aid);

  // column: the action tools for metadata (like delete)
  var action_tools_container = document.createElement('td');
  this.get_attribute_action_tools(action_tools_container, aid);
  tr.appendChild( action_tools_container );

  // column: aid (i.e. attribute id)
  tr.appendChild( this.html_element('td', aid) );

  // column: name
  var aname_container = document.createElement('td');
  var aname = document.createElement('input');
  aname.setAttribute('type', 'text');
  aname.setAttribute('data-aid', aid);
  aname.setAttribute('data-varname', 'aname');
  aname.setAttribute('value', this.d.store.attribute[aid].aname);
  aname.addEventListener('change', this.attribute_on_change.bind(this));
  aname_container.appendChild(aname);
  tr.appendChild(aname_container);

  // column: anchor
  var anchor_select = document.createElement('select');
  anchor_select.setAttribute('data-aid', aid);
  anchor_select.setAttribute('data-varname', 'anchor_id');
  anchor_select.setAttribute('style', 'width:12em;');
  anchor_select.addEventListener('change', this.attribute_on_change.bind(this));
  var option_selected = false;
  for ( var anchor_id in _VIA_ATTRIBUTE_ANCHOR ) {
    if ( _VIA_ATTRIBUTE_ANCHOR[anchor_id] !== '__FUTURE__' ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', anchor_id);
      oi.innerHTML = _VIA_ATTRIBUTE_ANCHOR[anchor_id];

      if ( anchor_id === this.d.store.attribute[aid].anchor_id ) {
        oi.setAttribute('selected', '');
        option_selected = true;
      }
      anchor_select.appendChild(oi);
    }
  }
  var anchor = document.createElement('td');
  anchor.appendChild( anchor_select );
  tr.appendChild( anchor );
  if ( ! option_selected ) {
    anchor_select.selectedIndex = -1;
  }

  // column: input type
  var type_select = document.createElement('select');
  type_select.setAttribute('data-aid', aid);
  type_select.setAttribute('data-varname', 'type');
  type_select.addEventListener('change', this.attribute_update_type.bind(this));
  var type_str;
  for ( type_str in _VIA_ATTRIBUTE_TYPE ) {
    var oi = document.createElement('option');
    oi.setAttribute('value', _VIA_ATTRIBUTE_TYPE[type_str]);
    oi.innerHTML = type_str;

    if ( _VIA_ATTRIBUTE_TYPE[type_str] === this.d.store.attribute[aid].type ) {
      oi.setAttribute('selected', '');
    }
    type_select.appendChild(oi);
  }
  var type = document.createElement('td');
  type.appendChild( type_select );
  tr.appendChild( type );

  // column: description
  var desc_container = document.createElement('td');
  var desc = document.createElement('input');
  desc.setAttribute('type', 'text');
  desc.setAttribute('data-aid', aid);
  desc.setAttribute('data-varname', 'desc');
  desc.setAttribute('value', this.d.store.attribute[aid].desc);
  desc.addEventListener('change', this.attribute_on_change.bind(this));
  desc_container.appendChild(desc);
  tr.appendChild( desc_container );

  // column: options
  if ( this.d.store.attribute[aid].type === _VIA_ATTRIBUTE_TYPE.TEXT ) {
    // text has no defaults
    tr.appendChild( this.html_element('td', '-') );
  } else {
    var option_input = document.createElement('textarea');
    option_input.setAttribute('data-aid', aid);
    option_input.setAttribute('data-varname', 'options');
    option_input.setAttribute('placeholder', 'e.g. a,*b,c,d');
    option_input.setAttribute('title', 'Enter options as comma separated value with the default option prefixed using an *. For example: "a,*b,c"');
    option_input.addEventListener('change', this.attribute_on_change.bind(this));
    option_input.innerHTML = _via_util_obj_to_csv(this.d.store.attribute[aid].options,
                                                  this.d.store.attribute[aid].default_option_id);
    tr.appendChild( option_input );
  }

  // column: default value (only if other than TEXT)
  if ( this.d.store.attribute[aid].type === _VIA_ATTRIBUTE_TYPE.TEXT ) {
    // text has no defaults
    tr.appendChild( this.html_element('td', '-') );
  } else {
    var default_value = this.d.store.attribute[aid].options[ this.d.store.attribute[aid].default_option_id ];
    if ( typeof(default_value) === 'undefined' ) {
      tr.appendChild( this.html_element('td', 'Not Defined') );
    } else {
      tr.appendChild( this.html_element('td', default_value) );
    }
  }

  // column: preview of attribute
  var preview = document.createElement('td');
  preview.appendChild( this.get_attribute_html_element(aid) );
  tr.appendChild(preview);

  return tr;
}

_via_editor.prototype.get_attribute_html_element = function(aid) {
  var dval  = this.d.store.attribute[aid].default_option_id;
  var atype = this.d.store.attribute[aid].type;
  var el;

  switch(atype) {
  case _VIA_ATTRIBUTE_TYPE.TEXT:
    el = document.createElement('textarea');
    break;

  case _VIA_ATTRIBUTE_TYPE.SELECT:
    el = document.createElement('select');

    for ( var oid in this.d.store.attribute[aid].options ) {
      var oi = document.createElement('option');
      oi.setAttribute('value', oid);
      oi.innerHTML = this.d.store.attribute[aid].options[oid];
      if ( oid === this.d.store.attribute[aid].default_option_id ) {
        oi.setAttribute('selected', 'true');
      }
      el.appendChild(oi);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.RADIO:
    el = document.createElement('div');

    for ( var oid in this.d.store.attribute[aid].options ) {
      var radio = document.createElement('input');
      radio.setAttribute('type', 'radio');
      radio.setAttribute('value', oid);
      radio.setAttribute('data-aid', aid);
      radio.setAttribute('name', this.d.store.attribute[aid].aname);
      if ( oid === this.d.store.attribute[aid].default_option_id ) {
        radio.setAttribute('checked', 'true');
      }

      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(radio);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  case _VIA_ATTRIBUTE_TYPE.CHECKBOX:
    el = document.createElement('div');

    for ( var oid in this.d.store.attribute[aid].options ) {
      var checkbox = document.createElement('input');
      checkbox.setAttribute('type', 'checkbox');
      checkbox.setAttribute('value', oid);
      checkbox.setAttribute('data-aid', aid);
      checkbox.setAttribute('name', this.d.store.attribute[aid].aname);
      if ( oid === this.d.store.attribute[aid].default_option_id ) {
        checkbox.setAttribute('checked', 'true');
      }

      var label = document.createElement('label');
      label.innerHTML = this.d.store.attribute[aid].options[oid];

      var br = document.createElement('br');
      el.appendChild(checkbox);
      el.appendChild(label);
      el.appendChild(br);
    }
    break;

  default:
    console.log('attribute type ' + atype + ' not implemented yet!');
    var el = document.createElement('span');
    el.innerHTML = aval;
  }
  el.setAttribute('data-aid', aid);
  return el;
}

_via_editor.prototype.get_attribute_name_entry_panel = function() {
  var c = document.createElement('div');
  c.setAttribute('class', 'attribute_entry');

  this.new_attribute_name_input = document.createElement('input');
  this.new_attribute_name_input.setAttribute('type', 'text');
  this.new_attribute_name_input.setAttribute('placeholder', 'name of new attribute');
  c.appendChild(this.new_attribute_name_input);

  var add = document.createElement('button');
  add.setAttribute('class', 'text-button');
  add.innerHTML = 'Create';
  add.addEventListener('click', this.on_attribute_create.bind(this));
  c.appendChild(add);

  return c;
}

_via_editor.prototype.get_attribute_action_tools = function(container, aid) {
  var del = _via_util_get_svg_button('micon_delete', 'Delete Attribute');
  del.setAttribute('data-aid', aid);
  del.addEventListener('click', this.attribute_del.bind(this));
  container.appendChild(del);
}

_via_editor.prototype.update_attribute_for = function(aid) {
  var tbody = this.attribute_view.getElementsByTagName('tbody')[0];
  var n = tbody.childNodes.length;
  var i;
  for ( i = 0; i < n; ++i ) {
    if ( tbody.childNodes[i].dataset.aid === aid ) {
      var new_attribute = this.get_attribute(aid);
      tbody.replaceChild( new_attribute, tbody.childNodes[i] );
      break;
    }
  }
}

_via_editor.prototype.attribute_on_change = function(e) {
  var varname = e.target.dataset.varname;
  var vartype = e.target.type;
  var aid = e.target.dataset.aid;

  switch(vartype) {
  case 'text':
    this.d.store.attribute[aid][varname] = e.target.value;
    break;
  case 'textarea':
    if ( varname === 'options' ) {
      var options_csv = e.target.value;
      this.d.attribute_update_options_from_csv(aid, options_csv).then(function(ok) {
      }.bind(this));
    }
    break;
  case 'select':
  case 'select-one':
    if ( varname === 'anchor_id' ) {
      var new_anchor_id = e.target.options[e.target.selectedIndex].value;
      this.d.attribute_update_anchor_id(aid, new_anchor_id);
    }
    break;
  default:
    console.warn('Unknown varname=' + varname + ', vartype=' + vartype);
  }

  this.attributes_update();
}

//
// Listeners for data update events
//
_via_editor.prototype.metadata_del = function(e) {
  var fid = e.currentTarget.dataset.fid;
  var mid = e.currentTarget.dataset.mid;
  this.d.metadata_del(fid, mid).then( function(ok) {
    // we don't need to do anything when metadata delete is successful
  }.bind(this), function(err) {
    console.log(err)
  }.bind(this));
}

_via_editor.prototype.metadata_edit = function(e) {
  var fid = e.target.parentNode.dataset.fid;
  var mid = e.target.parentNode.dataset.mid;
  console.log('@todo: edit metadata: fid=' + fid + ', mid=' + mid);
}

_via_editor.prototype.on_attribute_create = function(e) {
  var new_attribute_name = this.new_attribute_name_input.value;
  this.d.attribute_add(new_attribute_name,
                       _VIA_DEFAULT_ATTRIBUTE_ANCHOR_ID,
                       _VIA_ATTRIBUTE_TYPE.TEXT).then( function(ok) {
    this.attributes_update();
    // attribute was added
  }.bind(this), function(err) {
    console.log(err);
  }.bind(this));
}

_via_editor.prototype.attribute_del = function(e) {
  var aid = e.currentTarget.dataset.aid;
  this.d.attribute_del(aid).then( function(ok) {
    // we don't need to do anything when attribute delete is successful
  }.bind(this), function(err) {
    console.log(err)
  }.bind(this));
}

_via_editor.prototype.attribute_update_options = function(e) {
  var aid = e.target.dataset.aid;
  var new_options_csv = e.target.value;
  this.d.attribute_update_options(aid, new_options_csv);
}

_via_editor.prototype.attribute_update_type = function(e) {
  var aid = e.target.dataset.aid;
  var new_type = parseInt(e.target.options[ e.target.selectedIndex ].value);
  this.d.attribute_update_type(aid, new_type);
}

_via_editor.prototype.on_event_attribute_update = function(data, event_payload) {
  this.attributes_update();
}

_via_editor.prototype.on_event_attribute_del = function(data, event_payload) {
  this.attributes_update();
}

_via_editor.prototype.on_event_attribute_add = function(data, event_payload) {
  this.attributes_update();
}

_via_editor.prototype.on_event_metadata_add = function(data, event_payload) {
  //this.metadata_update();
}

_via_editor.prototype.on_event_metadata_del = function(data, event_payload) {
  //this.metadata_update();
}

_via_editor.prototype.on_event_file_show = function(data, event_payload) {
  //this.metadata_update();
}
</script>
<!-- END: Contents of file: js/_via_editor.js-->
<!-- START: Contents of file: js/_via_debug_project.js-->
<script>
var _via_dp = []; // debug project

//
// speaker diarisation : wikimedia video interview
//
_via_dp[0] = {};
_via_dp[0]['store'] = {};
_via_dp[0]['store']['project'] = {
  'pid': '__VIA_PROJECT_ID__',
  'rev': '__VIA_PROJECT_REV_ID__',
  'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
  'pname':   'VIA Debug Project',
  'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
  'created': Date.now(),
  'data_format_version': '3.1.0',
};
_via_dp[0]['store']['config'] = {
  'file': {
    'loc_prefix': {
      '1':'/data/datasets/via/via-3.x.y/speaker_diarisation/',
      '2':'',
      '3':'',
      '4':'',
    },
  },
  'ui': {
    'file_content_align':'center'
  }
};
_via_dp[0]['store']['attribute'] = {
  '1': {
    'aname':'speaker-id',
    'anchor_id':'FILE1_Z2_XY0',
    'type':1,
    'desc':'Name of the speaker',
    'options':{},
    'default_option_id':'',
  },
  '2': {
    'aname':'name',
    'anchor_id':'FILE1_Z0_XY1',
    'type':4,
    'desc':'Name of Object in Video Frame',
    'options':{
      '1':'cat',
      '2':'dog',
      '3':'apple',
    },
    'default_option_id':'2',
  },
};
_via_dp[0]['store']['file'] = {
  '1':{
    'fid':1,
    'fname':'Wikimedia_Interview_Romaine.mp4',
    'type':4,
    'loc':1,
    'src':'Wikimedia_Interview_Romaine.mp4',
  },
  '2':{
    'fid':2,
    'fname':'Wikimedia_HelenHillInterview.mp4',
    'type':4,
    'loc':1,
    'src':'Wikimedia_HelenHillInterview.mp4',
  },
  '3':{
    'fid':3,
    'fname':'Wikimedia_Chomsky_On_Obama_budget.mp4',
    'type':4,
    'loc':1,
    'src':'Wikimedia_Chomsky_On_Obama_budget.mp4',
  },
  '4':{
    'fid':4,
    'fname':'Wikimedia_Anne_Gaylor_A_Second_Look_At_Religion.mp4',
    'type':4,
    'loc':1,
    'src':'Wikimedia_Anne_Gaylor_A_Second_Look_At_Religion.mp4',
  },
};

_via_dp[0]['store']['view'] = {
  '1': {
    'fid_list':[1],
  },
  '2': {
    'fid_list':[2],
  },
  '3': {
    'fid_list':[3],
  },
  '4': {
    'fid_list':[4],
  },
};

_via_dp[0]['store']['vid_list'] = ['1','2','3','4'];
_via_dp[0]['store']['metadata'] = {
  '-glfwaaX': {
    'vid': '1',
    'flg': 0,
    'z': [1.343, 10.592],
    'xy': [],
    'av': {
      '1':'spk1'
    }
  },
  '+mHHT-tg': {
    'vid': '1',
    'flg': 0,
    'z': [12.413, 20.592],
    'xy': [],
    'av': {
      '1':'spk1'
    }
  },
  'ed+wsOZZ': {
    'vid': '1',
    'flg': 0,
    'z': [22.872, 24.946],
    'xy': [],
    'av': {
      '1':'spk2'
    }
  },
  'mxCVj1qz': {
    'vid': '1',
    'flg': 0,
    'z': [25.215, 29.132],
    'xy': [],
    'av': {
      '1':'spk2'
    }
  },
'kHRoMie1': {
    'vid': '1',
    'flg': 0,
    'z': [1.564, 5.852],
    'xy': [],
    'av': {
      '1':'spk3'
    }
  },
  'QhKrsZlV': {
    'vid': '1',
    'flg': 0,
    'z': [11.294, 22.217],
    'xy': [],
    'av': {
      '1':'spk4'
    }
  },
  'uzhWgk75': {
    'vid': '1',
    'flg': 0,
    'z': [16.861, 28.410],
    'xy': [],
    'av': {
      '1':'spk5'
    }
  },
  'mZqTKmHy': {
    'vid': '1',
    'flg': 0,
    'z': [1.311, 7.402],
    'xy': [],
    'av': {
      '1':'spk6'
    }
  },
  '6wu+scg2': {
    'vid': '1',
    'flg': 0,
    'z': [17.918, 29.482],
    'xy': [],
    'av': {
      '1':'spk6'
    }
  },
  'HLO-6liP': {
    'vid': '1',
    'flg': 0,
    'z': [9.418, 14.602],
    'xy': [],
    'av': {
      '1':'spk6'
    }
  },
};

//
// speaker diarisation : Drone on final at LAX 	(PRFlyer, atclive)
// source: https://forums.liveatc.net/index.php?action=dlattach;topic=15046.0;attach=10186
// date: 2018-12-28 22:43:56
_via_dp[1] = {};
_via_dp[1]['store'] = {};
_via_dp[1]['store']['project'] = {
  'pid': '__VIA_PROJECT_ID__',
  'rev': '__VIA_PROJECT_REV_ID__',
  'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
  'pname':   'Audio Annotation',
  'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
  'created': Date.now(),
  'data_format_version': '3.1.1',
  'vid_list': ['1', '2', '3', '4'],
};

_via_dp[1]['store']['config'] = {
  'file': {
    'loc_prefix': {
      '1':'',
      '2':'',
      '3':'file:///home/tlm/code/via3/via/via-3.x.y/data/sample_audio/',
      '4':'',
    },
  },
  'ui': {
    'file_content_align':'center'
  }
};

_via_dp[1]['store']['attribute'] = {
  '1': {
    'aname':'Speaker',
    'anchor_id':'FILE1_Z2_XY0',
    'type':1,
    'desc':'Speaker',
    'options':{},
    'default_option_id':'',
  },
  '2': {
    'aname':'transcription',
    'anchor_id':'FILE1_Z2_XY0',
    'type':1,
    'desc':'Manual Transcription of Audio Conversation',
    'options':{},
    'default_option_id':'',
  },
  '3': {
    'aname':'is_audio_clear',
    'anchor_id':'FILE1_Z2_XY0',
    'type':3,
    'desc':'Is the audio conversation clear to understand?',
    'options':{
      '0':'No',
      '1':'Yes',
    },
    'default_option_id':'1',
  },
};
_via_dp[1]['store']['file'] = {
  '1':{
    'fid':1,
    'fname':'Drone_liveatc_PRFlyer.mp3',
    'type':8,
    'loc':3,
    'src':'Drone_liveatc_PRFlyer.mp3',
  },
  '2':{
    'fid':2,
    'fname':'https://upload.wikimedia.org/wikipedia/commons/c/ce/Interview_Debora_Weber-Wulff_2.oga',
    'type':8,
    'loc':2,
    'src':'https://upload.wikimedia.org/wikipedia/commons/c/ce/Interview_Debora_Weber-Wulff_2.oga',
  },
  '3':{
    'fid':3,
    'fname':'The_ENIAC_Programmers_%28As_Told_By_U.S._Chief_Technology_Officer_Megan_Smith%29.ogg',
    'type':8,
    'loc':2,
    'src':'https://upload.wikimedia.org/wikipedia/commons/1/15/The_ENIAC_Programmers_%28As_Told_By_U.S._Chief_Technology_Officer_Megan_Smith%29.ogg',
  },
  '4':{
    'fid':4,
    'fname':'Calibration.ogg',
    'type':8,
    'loc':2,
    'src':'https://upload.wikimedia.org/wikipedia/commons/8/81/Heart_Monitor_Beep--freesound.org.oga',
  },

};

_via_dp[1]['store']['view'] = {
  '1': {
    'fid_list':[1],
  },
  '2': {
    'fid_list':[2],
  },
  '3': {
    'fid_list':[3],
  },
  '4': {
    'fid_list':[4],
  },
};

_via_dp[1]['store']['metadata'] = {
  '-glfwaaX': {
    'vid': '1',
    'flg': 0,
    'z': [0, 5.5],
    'xy': [],
    'av': {
      '1':'Pilot',
      '2':'?',
      '3':'0',
    }
  },
  '+mHHT-tg': {
    'vid': '1',
    'flg': 0,
    'z': [6.0, 11.0],
    'xy': [],
    'av': {
      '1':'ATC',
      '2':'Clear to land',
      '3':'1',
    }
  },
  'ed+wsOZZ': {
    'vid': '1',
    'flg': 0,
    'z': [11.0, 18.0],
    'xy': [],
    'av': {
      '1':'Pilot',
      '2':'Tower to PNR 102 ?',
      '3':'0',
    }
  },
  'mxCVj1qz': {
    'vid': '1',
    'flg': 0,
    'z': [18.0, 18.9],
    'xy': [],
    'av': {
      '1':'ATC',
      '2':'?',
      '3':'0',
    }
  },
'kHRoMie1': {
    'vid': '1',
    'flg': 0,
    'z': [19.0, 30.0],
    'xy': [],
    'av': {
      '1':'Pilot',
      '2':'we have a drone on our right approximately ?',
      '3':'1',
    }
  },
  'QhKrsZlV': {
    'vid': '1',
    'flg': 0,
    'z': [32.0, 38.0],
    'xy': [],
    'av': {
      '1':'ATC',
      '2':'? do you have a colour and type',
      '3':'1',
    }
  },
  'uzhWgk75': {
    'vid': '1',
    'flg': 0,
    'z': [38.0, 40.0],
    'xy': [],
    'av': {
      '1':'Pilot',
      '2':'its dark green',
      '3':'1',
    }
  },
  "2_Z+LXp1s8": {
    "vid": "2",
    "flg": 0,
    "z": [
      0,
      1.183
    ],
    "xy": [],
    "av": {
      "1": "Interviewer"
    }
  },
  "2_AG1+bmSA": {
    "vid": "2",
    "flg": 0,
    "z": [
      1.137,
      2.436121
    ],
    "xy": [],
    "av": {
      "1": "Prof. Debora"
    }
  },
  "2_S5L7szM-": {
    "vid": "2",
    "flg": 0,
    "z": [
      2.436,
      3.816733
    ],
    "xy": [],
    "av": {
      "1": "Interviewer"
    }
  },
  "2_cMYHLELJ": {
    "vid": "2",
    "flg": 0,
    "z": [
      4.017,
      5.624805
    ],
    "xy": [],
    "av": {
      "1": "Prof. Debora"
    }
  },
  "2_DaiuIloC": {
    "vid": "2",
    "flg": 0,
    "z": [
      5.921,
      15.101221
    ],
    "xy": [],
    "av": {
      "1": "Interviewer"
    }
  },
  "2_zp-ru14v": {
    "vid": "2",
    "flg": 0,
    "z": [
      15.101,
      41.008681
    ],
    "xy": [],
    "av": {
      "1": "Prof. Debora"
    }
  },
  "2_dIFoc30m": {
    "vid": "2",
    "flg": 0,
    "z": [
      41.147,
      62.644961
    ],
    "xy": [],
    "av": {
      "1": "Interviewer"
    }
  },
  "3_Xp-au12v": {
    "vid": "3",
    "flg": 0,
    "z": [
      2.101,
      6.008681
    ],
    "xy": [],
    "av": {
      "1": "A"
    }
  },
  "3_sIxEc10m": {
    "vid": "3",
    "flg": 0,
    "z": [
      16.147,
      27.644961
    ],
    "xy": [],
    "av": {
      "1": "B"
    }
  },
};

//
// human activity
// source: https://commons.wikimedia.org/wiki/File:Alioli.ogv
_via_dp[2] = {};
_via_dp[2]['store'] = {};
_via_dp[2]['store']['project'] = {
//  'pid': '__VIA_PROJECT_ID__',
//  'rev': '__VIA_PROJECT_REV_ID__',
  //  'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
  "pid":"71578187-3cd3-45d0-8198-7c441fbc06af",
  "rev":"1",
  "rev_timestamp":"1561373349251",
  'pname':   'Video Annotation Project',
  'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
  'created': Date.now(),
  'data_format_version': '3.1.1',
  'vid_list': ['1', '2', '3', '4'],
};
_via_dp[2]['store']['config'] = {
  'file': {
    'loc_prefix': {
      '1':'',
      '2':'', // https://upload.wikimedia.org/wikipedia/commons/',
      '3':'file:///home/tlm/data/via/debug/', // file:///home/tlm/data/via/debug/
      '4':'',
    },
  },
  'ui': {
    'file_content_align':'center'
  }
};
_via_dp[2]['store']['attribute'] = {
  '1': {
    'aname':'Activity',
    'anchor_id':'FILE1_Z2_XY0',
    'type':4,
    'desc':'Activity',
    'options':{
      '1':'1. break egg',
      '2':'2. pour liquid',
      '3':'3. cut garlic',
      '4':'4. mix'
    },
    'default_option_id':'',
  },
  '5': {
    'aname':'Difficult',
    'anchor_id':'FILE1_Z2_XY0',
    'type':4,
    'desc':'Is the activity difficult?',
    'options':{ 'yes':'Yes', 'no':'No' },
    'default_option_id':'',
  },
  '2': {
    'aname':'Object',
    'anchor_id':'FILE1_Z1_XY1',
    'type':4,
    'desc':'Name of Object',
    'options':{
      '1':'Egg',
      '2':'Bottle',
      '3':'Mixer',
      '4':'Cup',
      '5':'Garlic',
      '6':'Knife',
      '7':'Tea Bag',
    },
    'default_option_id':'',
  },
  '3': {
    'aname':'Type',
    'anchor_id':'FILE1_Z1_XY1',
    'type':3,
    'desc':'Type of Object',
    'options':{
      '1':'Ingredient',
      '2':'Tool',
      '3':'Unknown',
    },
    'default_option_id':'3'
  }
};
_via_dp[2]['store']['file'] = {
  '1':{
    'fid':1,
    'fname':'Alioli_Wikimedia_Wardtmar.mp4',
    'type':4,
    'loc':3,
    'src':'Alioli_Wikimedia_Wardtmar.mp4',
  },
  '2':{
    'fid':2,
    'fname':'PreparingTea.mp4',
    'type':4,
    'loc':1,
    'src':'PreparingTea.mp4',
  },
  '3':{
    'fid':3,
    'fname':'Tea.mp4',
    'type':4,
    'loc':1,
    'src':'PreparingTea.mp4',
  },
  '4':{
    'fid':4,
    'fname':'5/54/04-European_Robin-nX-1.webm',
    'type':4,
    'loc':3,
    'src':'04-European_Robin-nX-1.webm',
  }
};

_via_dp[2]['store']['view'] = {
  '1': {
    'fid_list':[1],
  },
  '2': {
    'fid_list':[2],
  },
  '3': {
    'fid_list':[3],
  },
  '4': {
    'fid_list':[4],
  }
};

_via_dp[2]['store']['metadata'] = {
  '-glfwaaX': {
    'vid': '1',
    'flg': 0,
    'z': [2, 6.5],
    'xy': [],
    'av': {
      '1':'1',
    }
  },
  '+mHHT-tg': {
    'vid': '1',
    'flg': 0,
    'z': [9, 20],
    'xy': [],
    'av': {
      '1':'2',
    }
  },
  'ed+wsOZZ': {
    'vid': '1',
    'flg': 0,
    'z': [24, 26],
    'xy': [],
    'av': {
      '1':'2',
    }
  },
  'mxCVj1qz': {
    'vid': '1',
    'flg': 0,
    'z': [32, 44.3],
    'xy': [],
    'av': {
      '1':'3',
    }
  },
  'kHRoMie1': {
    'vid': '1',
    'flg': 0,
    'z': [64, 128],
    'xy': [],
    'av': {
      '1':'4',
    }
  },
  'fH-oMre1': {
    'vid': '1',
    'flg': 0,
    'z': [0.917],
    'xy': [4, 263, 184, 17, 13],
    'av': {
      '2':'1',
      '3':'1',
    }
  },
  'vPao-re8': {
    'vid': '1',
    'flg': 0,
    'z': [0.917],
    'xy': [2, 343, 730, 60, 160],
    'av': {
      '2':'2',
      '3':'1',
    }
  },
  'x-PoM9ea': {
    'vid': '1',
    'flg': 0,
    'z': [0.917],
    'xy': [7, 225, 133, 177, 177, 195, 188, 246, 138],
    'av': {
      '2':'6',
      '3':'2',
    }
  },
  'mR8ks-Aa': {
    'vid': '1',
    'flg': 0,
    'z': [7.208],
    'xy': [4, 328, 110, 25, 21],
    'av': {
      '2':'1',
      '3':'1',
    }
  },
  'Yh7Klow6': {
    'vid': '1',
    'flg': 0,
    'z': [21.52],
    'xy': [2, 315, 52, 80, 155],
    'av': {
      '2':'2',
      '3':'1',
    }
  },
  'A7jfx8PX': {
    'vid': '2',
    'flg': 0,
    'z': [3, 8],
    'xy': [],
    'av': {
      '1':'1. Add water',
    }
  },
  'M8kl0+Xe': {
    'vid': '2',
    'flg': 0,
    'z': [16, 28],
    'xy': [],
    'av': {
      '1':'2. Warm water',
    }
  },
  'yp-X6Hjy': {
    'vid': '2',
    'flg': 0,
    'z': [35, 39],
    'xy': [],
    'av': {
      '1':'3. Add tea bag',
    }
  },
  'jU4+xekA': {
    'vid': '2',
    'flg': 0,
    'z': [35],
    'xy': [2, 265, 124, 92, 120],
    'av': {
      '2':'7',
      '3':'1',
    }
  },
  'Hycx5-Sa': {
    'vid': '2',
    'flg': 0,
    'z': [0.75],
    'xy': [2, 144, 132, 150, 138],
    'av': {
      '2':'4',
      '3':'2',
    }
  },
};

//
// Image Pair Annotation: MRI Stenosis
//
_via_dp[3] = {};
_via_dp[3]['store'] = {};
_via_dp[3]['store']['project'] = {
  'pid': '__VIA_PROJECT_ID__',
  'rev': '__VIA_PROJECT_REV_ID__',
  'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
  'pname': 'Demo Pair Annotation',
  'pdesc': 'Demonstration of annotation of an image pair',
  'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
  'created': Date.now(),
  'data_format_version': '3.1.1',
  'vid_list': ['1', '2', '3', '4'],
};
_via_dp[3]['store']['config'] = {
  'file': {
    'loc_prefix': {
      '1':'',
      '2':'https://upload.wikimedia.org/wikipedia/commons/thumb/',
      '3':'',
      '4':'',
    },
  },
  'ui': {
    'file_content_align':'center'
  }
};
_via_dp[3]['store']['attribute'] = {
  '1': {
    'aname':'beautiful',
    'anchor_id':'FILEN_Z0_XY0',
    'type':3,
    'desc':'In your opinion, which image is more beautiful?',
    'options':{
      '1': 'Image 1',
      '2': 'Image 2',
      '0': 'Not Sure',
    },
    'default_option_id':'',
  },
  '2': {
    'aname':'why',
    'anchor_id':'FILEN_Z0_XY0',
    'type':1,
    'desc':'In few words, explain your response',
    'options':{},
    'default_option_id':'',
  },
};

_via_dp[3]['store']['file'] = {
  '1':{
    'fid':1,
    'fname':'Red-billed oxpecker (Buphagus erythrorhynchus) on impala (Aepyceros melampus).jpg',
    'type':2,
    'loc':2,
    'src':'d/d5/Red-billed_oxpecker_(Buphagus_erythrorhynchus)_on_impala_(Aepyceros_melampus).jpg/320px-Red-billed_oxpecker_(Buphagus_erythrorhynchus)_on_impala_(Aepyceros_melampus).jpg',
  },
  '2':{
    'fid':2,
    'fname':'Balloon over Luxor - Egypt denoised.jpg',
    'type':2,
    'loc':2,
    'src':'4/41/Balloon_over_Luxor_-_Egypt_denoised.jpg/320px-Balloon_over_Luxor_-_Egypt_denoised.jpg',
  },
  '3':{
    'fid':3,
    'fname':'Castillo de Montuenga, Montuenga de Soria, Soria, España, 2017-05-23, DD 04.jpg',
    'type':2,
    'loc':2,
    'src':'d/d6/Castillo_de_Montuenga%2C_Montuenga_de_Soria%2C_Soria%2C_España%2C_2017-05-23%2C_DD_04.jpg/320px-Castillo_de_Montuenga%2C_Montuenga_de_Soria%2C_Soria%2C_España%2C_2017-05-23%2C_DD_04.jpg',
  },
  '4':{
    'fid':4,
    'fname':'Paradise shelduck portrait, New Zealand.jpg',
    'type':2,
    'loc':2,
    'src':'a/ad/Paradise_shelduck_portrait%2C_New_Zealand.jpg/320px-Paradise_shelduck_portrait%2C_New_Zealand.jpg',
  },
};

_via_dp[3]['store']['view'] = {
  '1': {
    'fid_list': [ 1, 2 ]
  },
  '2': {
    'fid_list': [ 3, 4 ]
  },
  '3': {
    'fid_list': [ 2, 3 ]
  },
  '4': {
    'fid_list': [ 1, 4 ]
  },
  // add a view for each file (needed when individual images are annotated)
  // these views are not visible as they are not present in ['project']['vid_list']
  '5': { 'fid_list': [1] },
  '6': { 'fid_list': [2] },
  '7': { 'fid_list': [3] },
  '8': { 'fid_list': [4] },
};
_via_dp[3]['store']['metadata'] = {};

//
// Image Annotations
//
_via_dp[4] = {};
_via_dp[4]['store'] = {};
_via_dp[4]['store']['project'] = {
  'pid': '__VIA_PROJECT_ID__',
  'rev': '__VIA_PROJECT_REV_ID__',
  'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
  'pname':   'Image Annotation',
  'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
  'created': Date.now(),
  'data_format_version': '3.1.1',
  'vid_list':['1', '2'],
};
_via_dp[4]['store']['config'] = {
  'file': {
    'loc_prefix': {
      '1':'',
      '2':'',
      '3':'../../data/sample_img/',
      '4':'',
    },
  },
  'ui': {
    'file_content_align':'center'
  }
};
_via_dp[4]['store']['attribute'] = {
  '1':{
    'aname':'name',
    'anchor_id':'FILE1_Z0_XY1',
    'type': 3,
    'desc': 'Name of Object',
    'options': {
      '0': 'Swan',
      '1': 'Human',
      '2': 'Unknown',
    },
    'default_option_id': ''
  },
  '2': {
    'aname':'Caption',
    'anchor_id':'FILE1_Z0_XY0',
    'type':1,
    'desc':'Image Caption',
    'options':{},
    'default_option_id':'',
  },
};

_via_dp[4]['store']['file'] = {
  '1': {
    'fid': 1,
    'fname': 'adutta_swan.jpg',
    'type': 2,
    'loc': 3,
    'src': 'adutta_swan.jpg',
  },
  '2': {
    'fid': 2,
    'fname': 'wikimedia_death_of_socrates.jpg',
    'type': 2,
    'loc': 3,
    'src': 'wikimedia_death_of_socrates.jpg',
  },
};

_via_dp[4]['store']['view'] = {
  '1': {
    'fid_list': [
      1,
    ]
  },
  '2': {
    'fid_list': [
      2,
    ]
  },
};
_via_dp[4]['store']['metadata'] = {
  '1_rozl5hQT':{
    'vid':'1',
    'flg':0,
    'z':[],
    'xy':[7,106.131,158.392,87.638,214.271,209.849,281.005,375.879,271.357,383.518,224.724,402.412,175.678,373.869,128.643,310.352,134.673,306.734,190.553,237.99,170.452],
    'av':{
      '1':'0',
    },
  },
  '1_n8iiCJ6Y':{
    'vid':'1',
    'flg':0,
    'z':[],
    'xy':[],
    'av':{
      '2':'A white swan in Geneva lake.',
    },
  },
};
</script>
<!-- END: Contents of file: js/_via_debug_project.js-->
<!-- START: Contents of file: js/_via.js-->
<script>
/**
 *
 * @class
 * @classdesc VIA
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 12 May 2019
 *
 */

'use strict'

function _via(via_container) {
  this._ID = '_via';

  console.log('Initializing VGG Image Annotator (VIA) version ' + _VIA_VERSION)
  this.via_container = via_container;

  this.d  = new _via_data();
  var conf = { 'ENDPOINT': _VIA_REMOTE_STORE };
  this.s  = new _via_share(this.d, conf);

  if ( typeof(_VIA_DEBUG) === 'undefined' || _VIA_DEBUG === true ) {
    // ADD DEBUG CODE HERE (IF NEEDED)
  }

  //// define the html containers
  this.control_panel_container = document.createElement('div');
  this.control_panel_container.setAttribute('id', 'via_control_panel_container');
  this.via_container.appendChild(this.control_panel_container);

  this.view_container = document.createElement('div');
  this.view_container.setAttribute('id', 'view_container');
  this.via_container.appendChild(this.view_container);

  this.editor_container = document.createElement('div');
  this.editor_container.setAttribute('id', 'editor_container');
  this.editor_container.classList.add('hide');
  this.via_container.appendChild(this.editor_container);

  this.message_container = document.createElement('div');
  this.message_container.setAttribute('id', '_via_message_container');
  this.message_container.setAttribute('class', 'message_container');
  this.message_container.addEventListener('click', _via_util_msg_hide);
  this.message_panel = document.createElement('div');
  this.message_panel.setAttribute('id', '_via_message');
  this.message_container.appendChild(this.message_panel);
  this.via_container.appendChild(this.message_container);

  //// initialise content creators and managers
  this.ie = new _via_import_export(this.d);

  this.va = new _via_view_annotator(this.d, this.view_container);
  this.editor = new _via_editor(this.d, this.va, this.editor_container);

  this.view_manager_container = document.createElement('div');
  this.view_manager_container.setAttribute('id', 'view_manager_container');
  this.vm = new _via_view_manager(this.d, this.va, this.view_manager_container);
  this.vm._init();

  // control panel shows the view_manager_container
  this.cp = new _via_control_panel(this.control_panel_container, this);
  this.cp._set_region_shape('RECTANGLE');

  // event handlers for buttons in the control panel
  this.cp.on_event('region_shape', this._ID, function(data, event_payload) {
    this.va.set_region_draw_shape(event_payload.shape);
  }.bind(this));
  this.cp.on_event('editor_toggle', this._ID, function(data, event_payload) {
    this.editor.toggle();
  }.bind(this));
  this.cp.on_event('magnifier_toggle', this._ID, function(data, event_payload) {
    if(this.va.view_mode === _VIA_VIEW_MODE.IMAGE1) {
      this.va.file_annotator[0][0]._magnifier_toggle();
    }
  }.bind(this));
  this.cp.on_event('zoom_in', this._ID, function(data, event_payload) {
    if(this.va.view_mode === _VIA_VIEW_MODE.IMAGE1) {
      this.va.file_annotator[0][0]._zoom_in();
    }
  }.bind(this));
  this.cp.on_event('zoom_out', this._ID, function(data, event_payload) {
    if(this.va.view_mode === _VIA_VIEW_MODE.IMAGE1) {
      this.va.file_annotator[0][0]._zoom_out();
    }
  }.bind(this));
  this.cp.on_event('fit_screen', this._ID, function(data, event_payload) {
    if(this.va.view_mode === _VIA_VIEW_MODE.IMAGE1) {
      this.va.file_annotator[0][0]._zoom_fit_screen();
    }
  }.bind(this));

  // keyboard event handlers
  //this.via_container.focus()
  //this.via_container.addEventListener('keydown', this._keydown_handler.bind(this));
  window.addEventListener('keydown', this._keydown_handler.bind(this)); // @todo: should be attached only to VIA application container

  // update VIA version number
  var el = document.getElementById('via_page_container');
  var pages = el.getElementsByClassName('via_page');
  var n = pages.length;
  for ( var i = 0; i < n; ++i ) {
    if ( pages[i].dataset.pageid === 'page_about' ) {
      var content0 = pages[i].innerHTML;
      pages[i].innerHTML = content0.replace('__VIA_VERSION__', _VIA_VERSION);
    }
  }

  // load any external modules (e.g. demo) which should be defined as follows
  // function _via_load_submodules()
  if (typeof _via_load_submodules === 'function') {
    console.log('VIA submodule detected, invoking _via_load_submodules()');
    this._load_submodule = new Promise( function(ok_callback, err_callback) {
      try {
        _via_load_submodules.call(this);
      }
      catch(err) {
        console.warn('VIA submodule load failed: ' + err);
        err_callback(err);
      }
    }.bind(this));
  } else {
    // debug code (disabled for release)
    if ( typeof(_VIA_DEBUG) === 'undefined' || _VIA_DEBUG === true ) {
      //this.s.pull(''); // load shared project
      //this.d.project_load_json(_via_dp[2]['store']); // video
      //this.d.project_load_json(_via_dp[1]['store']); // audio
      //this.d.project_load_json(_via_dp[4]['store']); // image
      //this.d.project_load_json(_via_dp[3]['store']); // pair

      /*
      setTimeout( function() {
      //this.va.view_show('1');
      //this.editor.show();
      //this.cp._page_show_import_export();
      //this.cp._share_show_info();
      }.bind(this), 200);
      */
    }
  }

  // ready
  _via_util_msg_show(_VIA_NAME + ' (' + _VIA_NAME_SHORT + ') ' + _VIA_VERSION + ' ready.');
}

_via.prototype._hook_on_browser_resize = function() {
  if ( typeof(this.va.vid) !== 'undefined' ) {
    this.va.view_show(this.va.vid);
  }
}

_via.prototype._keydown_handler = function(e) {
  // avoid handling events when text input field is in focus
  if ( e.target.type !== 'text' &&
       e.target.type !== 'textarea'
     ) {
    this.va._on_event_keydown(e);
  }
}
</script>
<!-- END: Contents of file: js/_via.js-->

    <!-- Used by external programs (e.g. imfind) to create VIA3 application that automatically loads a project file -->
    <!-- <script>__VIA_SUBMODULE_LOADER__</script> -->

<!-- START: Contents of file: data/demo/_via_image_annotator.js-->
<script>
if ( typeof(_via_demo_data) === 'undefined' ) {
  var _via_demo_data = { 'image':[], 'audio':[], 'video':[] };
}

_via_demo_data['image'].push({
  "filename":"adutta_swan.jpg",
  "author":"Abhishek Dutta",
  "original_source": "from personal image library",
  "description":"Swan in Geneve Lake (Switzerland)",
  "src": ""});
</script>
<!-- END: Contents of file: data/demo/_via_image_annotator.js-->
<!-- START: Contents of file: src/js/_via_demo_image_annotator.js-->
<script>
/**
 *
 * @class
 * @classdesc Demo of image annotator
 * @author Abhishek Dutta <adutta@robots.ox.ac.uk>
 * @date 30 June 2019
 *
 * Audio 1
 * Video title: The making of Alioli in 2 minutes using standard kitchen equipment
 * Video filename: Alioli_Wikimedia_Wardtmar.mp4
 * Video author: Wardtmar at https://commons.wikimedia.org
 * Video source: https://commons.wikimedia.org/wiki/File:Alioli.ogv
 * Video date: 2018-12-28 22:43:56
 *
 * Video 2
 * Video source: https://commons.wikimedia.org/wiki/File:Grilledcheesesandwich.webm
 */

function _via_load_submodules() {
  console.log('Loading image annotator demo');
  _via_demo_project = { 'store':{} };
  _via_demo_project['store'] = {};
  _via_demo_project['store']['project'] = {
    'pid': '__VIA_PROJECT_ID__',
    'rev': '__VIA_PROJECT_REV_ID__',
    'rev_timestamp': '__VIA_PROJECT_REV_TIMESTAMP__',
    'pname': 'Demo Video Annotation',
    'creator': 'VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via)',
    'created': Date.now(),
    'data_format_version': '3.1.1',
    'vid_list':['1', '2'],
  };
  _via_demo_project['store']['config'] = {
    'file': {
      'loc_prefix': { '1':'', '2':'', '3':'', '4':'' }, // constants defined in _via_file._VIA_FILE_LOC
    },
    'ui': {
      'file_content_align':'center'
    }
  };
  _via_demo_project['store']['attribute'] = {
    '1': {
      'aname':'Name',
      'anchor_id':'FILE1_Z0_XY1',
      'type':1,
      'desc':'Name of Object',
      'options':{},
      'default_option_id':'',
    },
    '2': {
      'aname':'Caption',
      'anchor_id':'FILE1_Z0_XY0',
      'type':1,
      'desc':'Image Caption',
      'options':{},
      'default_option_id':'',
    },
  };
  _via_demo_project['store']['file'] = {
    '1':{
      'fid':1,
      'fname':_via_demo_data['image'][0]['filename'],
      'type':2,
      'loc':4,
      'src':_via_demo_data['image'][0]['src'],
    },
    '2':{
      'fid':2,
      'fname':'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/David_-_The_Death_of_Socrates.jpg/640px-David_-_The_Death_of_Socrates.jpg',
      'type':2,
      'loc':2,
      'src':'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/David_-_The_Death_of_Socrates.jpg/640px-David_-_The_Death_of_Socrates.jpg',
    },
  };

  _via_demo_project['store']['view'] = {
    '1': {
      'fid_list':[1],
    },
    '2': {
      'fid_list':[2],
    },
  };

  _via_demo_project['store']['metadata'] = {
    '1-glfwaaX': {
      'vid': '1',
      'flg': 0,
      'z': [],
      'xy': [2, 101, 125, 286, 146],
      'av': {
        '1':'Swan',
        '2':'A white swan in Geneva lake.',
      }
    },
    '2_WIpRNLvX': {
      'vid': '2',
      'flg': 0,
      'z': [],
      'xy': [2, 378.921,121.113,84.991,234.435],
      'av': {
        '1':'Socrates',
        '2':'Death of Socrates painting by David.',
      }
    },
    '2_eA5CqEPf': {
      'vid': '2',
      'flg': 0,
      'z': [],
      'xy': [2, 198.314,150.152,109.072,238.684],
      'av': {
        '1':'Plato',
      }
    },
  };

  this.d.store = _via_demo_project['store'];
  this.d._cache_update();
  this.vm._init();

  setTimeout( function() {
    this.va.view_show('1');
    if ( this.s ) {
      this.s._disable_share();
    }
    //this.editor.show();
  }.bind(this), 500);
}
</script>
<!-- END: Contents of file: src/js/_via_demo_image_annotator.js-->

    <script>
      var _VIA_DEBUG = false;
      var via_container = document.getElementById('via_container');
      var via = new _via(via_container);
      _via_util_page_show('page_demo_instructions');
    </script>
  </body>
</html>
